https://git.reactos.org/?p=reactos.git;a=commitdiff;h=39003dd408a605b4e66f1…
commit 39003dd408a605b4e66f1b040c7333b52e4d8158
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Sat Oct 23 13:55:02 2021 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun Nov 7 14:22:20 2021 +0100
[ADVAPI32] Properly implement CreateRestrictedToken
Instead of duplicating the token and masquerade it as if the token was filtered (which is a big hack), properly implement CreateRestrictedToken function by calling NtFilterToken which it'll create a restricted token for us.
---
dll/win32/advapi32/wine/security.c | 209 +++++++++++++++++++++++++++++--------
1 file changed, 166 insertions(+), 43 deletions(-)
diff --git a/dll/win32/advapi32/wine/security.c b/dll/win32/advapi32/wine/security.c
index 449cbd354b4..5651a92276a 100644
--- a/dll/win32/advapi32/wine/security.c
+++ b/dll/win32/advapi32/wine/security.c
@@ -481,56 +481,179 @@ SetThreadToken(IN PHANDLE ThreadHandle OPTIONAL,
return TRUE;
}
-/*************************************************************************
- * CreateRestrictedToken [ADVAPI32.@]
+/**
+ * @brief
+ * Creates a filtered token that is a restricted one
+ * of the regular access token. A restricted token
+ * can have disabled SIDs, deleted privileges and/or
+ * restricted SIDs added.
*
- * Create a new more restricted token from an existing token.
+ * @param[in] ExistingTokenHandle
+ * An existing handle to a token where it's to be
+ * filtered.
*
- * PARAMS
- * baseToken [I] Token to base the new restricted token on
- * flags [I] Options
- * nDisableSids [I] Length of disableSids array
- * disableSids [I] Array of SIDs to disable in the new token
- * nDeletePrivs [I] Length of deletePrivs array
- * deletePrivs [I] Array of privileges to delete in the new token
- * nRestrictSids [I] Length of restrictSids array
- * restrictSids [I] Array of SIDs to restrict in the new token
- * newToken [O] Address where the new token is stored
+ * @param[in] Flags
+ * Privilege flag options. This parameter argument influences how the token
+ * is filtered. Such parameter can be 0.
*
- * RETURNS
- * Success: TRUE
- * Failure: FALSE
+ * @param[in] DisableSidCount
+ * The count number of SIDs to disable.
+ *
+ * @param[in] SidsToDisable
+ * An array list with SIDs that have to be disabled in
+ * a token.
+ *
+ * @param[in] DeletePrivilegeCount
+ * The count number of privileges to be deleted.
+ *
+ * @param[in] PrivilegesToDelete
+ * An array list with privileges that have to be deleted
+ * in a token.
+ *
+ * @param[in] RestrictedSidCount
+ * The count number of restricted SIDs.
+ *
+ * @param[in] SidsToRestrict
+ * An array list with restricted SIDs to be added into
+ * the token. If the token already has restricted SIDs
+ * then the array provided by the caller is redundant
+ * information alongside with the existing restricted
+ * SIDs in the token.
+ *
+ * @param[out] NewTokenHandle
+ * The newly received handle to a restricted (filtered)
+ * token. The caller can use such handle to duplicate
+ * a new token.
+ *
+ * @return
+ * Returns TRUE if the function has successfully completed
+ * the operations, otherwise FALSE is returned to indicate
+ * failure. For further details the caller has to invoke
+ * GetLastError() API call for extended information
+ * about the failure.
*/
BOOL WINAPI CreateRestrictedToken(
- HANDLE baseToken,
- DWORD flags,
- DWORD nDisableSids,
- PSID_AND_ATTRIBUTES disableSids,
- DWORD nDeletePrivs,
- PLUID_AND_ATTRIBUTES deletePrivs,
- DWORD nRestrictSids,
- PSID_AND_ATTRIBUTES restrictSids,
- PHANDLE newToken)
-{
- TOKEN_TYPE type;
- SECURITY_IMPERSONATION_LEVEL level = SecurityAnonymous;
- DWORD size;
-
- FIXME("(%p, 0x%x, %u, %p, %u, %p, %u, %p, %p): stub\n",
- baseToken, flags, nDisableSids, disableSids,
- nDeletePrivs, deletePrivs,
- nRestrictSids, restrictSids,
- newToken);
-
- size = sizeof(type);
- if (!GetTokenInformation( baseToken, TokenType, &type, size, &size )) return FALSE;
- if (type == TokenImpersonation)
- {
- size = sizeof(level);
- if (!GetTokenInformation( baseToken, TokenImpersonationLevel, &level, size, &size ))
+ _In_ HANDLE ExistingTokenHandle,
+ _In_ DWORD Flags,
+ _In_ DWORD DisableSidCount,
+ _In_reads_opt_(DisableSidCount) PSID_AND_ATTRIBUTES SidsToDisable,
+ _In_ DWORD DeletePrivilegeCount,
+ _In_reads_opt_(DeletePrivilegeCount) PLUID_AND_ATTRIBUTES PrivilegesToDelete,
+ _In_ DWORD RestrictedSidCount,
+ _In_reads_opt_(RestrictedSidCount) PSID_AND_ATTRIBUTES SidsToRestrict,
+ _Outptr_ PHANDLE NewTokenHandle)
+{
+ NTSTATUS Status;
+ BOOL Success;
+ ULONG Index;
+ PTOKEN_GROUPS DisableSids = NULL;
+ PTOKEN_GROUPS RestrictedSids = NULL;
+ PTOKEN_PRIVILEGES DeletePrivileges = NULL;
+
+ /*
+ * Capture the elements we're being given from
+ * the caller and allocate the groups and/or
+ * privileges that have to be filtered in
+ * the token.
+ */
+ if (SidsToDisable != NULL)
+ {
+ DisableSids = (PTOKEN_GROUPS)LocalAlloc(LMEM_FIXED, DisableSidCount * sizeof(TOKEN_GROUPS));
+ if (DisableSids == NULL)
+ {
+ /* We failed, bail out */
+ SetLastError(RtlNtStatusToDosError(STATUS_INSUFFICIENT_RESOURCES));
return FALSE;
+ }
+
+ /* Copy the counter and loop the elements to copy the rest */
+ DisableSids->GroupCount = DisableSidCount;
+ for (Index = 0; Index < DisableSidCount; Index++)
+ {
+ DisableSids->Groups[Index].Sid = SidsToDisable[Index].Sid;
+ DisableSids->Groups[Index].Attributes = SidsToDisable[Index].Attributes;
+ }
+ }
+
+ if (PrivilegesToDelete != NULL)
+ {
+ DeletePrivileges = (PTOKEN_PRIVILEGES)LocalAlloc(LMEM_FIXED, DeletePrivilegeCount * sizeof(TOKEN_PRIVILEGES));
+ if (DeletePrivileges == NULL)
+ {
+ /* We failed, bail out */
+ SetLastError(RtlNtStatusToDosError(STATUS_INSUFFICIENT_RESOURCES));
+ Success = FALSE;
+ goto Cleanup;
+ }
+
+ /* Copy the counter and loop the elements to copy the rest */
+ DeletePrivileges->PrivilegeCount = DeletePrivilegeCount;
+ for (Index = 0; Index < DeletePrivilegeCount; Index++)
+ {
+ DeletePrivileges->Privileges[Index].Luid = PrivilegesToDelete[Index].Luid;
+ DeletePrivileges->Privileges[Index].Attributes = PrivilegesToDelete[Index].Attributes;
+ }
}
- return DuplicateTokenEx( baseToken, MAXIMUM_ALLOWED, NULL, level, type, newToken );
+
+ if (SidsToRestrict != NULL)
+ {
+ RestrictedSids = (PTOKEN_GROUPS)LocalAlloc(LMEM_FIXED, RestrictedSidCount * sizeof(TOKEN_GROUPS));
+ if (RestrictedSids == NULL)
+ {
+ /* We failed, bail out */
+ SetLastError(RtlNtStatusToDosError(STATUS_INSUFFICIENT_RESOURCES));
+ Success = FALSE;
+ goto Cleanup;
+ }
+
+ /* Copy the counter and loop the elements to copy the rest */
+ RestrictedSids->GroupCount = RestrictedSidCount;
+ for (Index = 0; Index < RestrictedSidCount; Index++)
+ {
+ RestrictedSids->Groups[Index].Sid = SidsToRestrict[Index].Sid;
+ RestrictedSids->Groups[Index].Attributes = SidsToRestrict[Index].Attributes;
+ }
+ }
+
+ /*
+ * Call the NT API to request a token filtering
+ * operation for us.
+ */
+ Status = NtFilterToken(ExistingTokenHandle,
+ Flags,
+ DisableSids,
+ DeletePrivileges,
+ RestrictedSids,
+ NewTokenHandle);
+ if (!NT_SUCCESS(Status))
+ {
+ /* We failed to do the job, bail out */
+ SetLastError(RtlNtStatusToDosError(Status));
+ Success = FALSE;
+ goto Cleanup;
+ }
+
+ /* If we reach here then we've successfully filtered the token */
+ Success = TRUE;
+
+Cleanup:
+ /* Free whatever we allocated before */
+ if (DisableSids != NULL)
+ {
+ LocalFree(DisableSids);
+ }
+
+ if (DeletePrivileges != NULL)
+ {
+ LocalFree(DeletePrivileges);
+ }
+
+ if (RestrictedSids != NULL)
+ {
+ LocalFree(RestrictedSids);
+ }
+
+ return Success;
}
/******************************************************************************
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=53db5377e2d1879c89efd…
commit 53db5377e2d1879c89efdf76a3828b0a04fb35b5
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Tue Oct 19 11:30:23 2021 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun Nov 7 14:14:18 2021 +0100
[NTOS:SE] Implement token filtering
This implements the support of token filtering within the kernel, where the kernel can create restricted tokens of regular ones on demand by the caller. The implementation can be accessed thorough a NT syscall, NtFilterToken, and a kernel mode routine, SeFilterToken.
---
ntoskrnl/se/token.c | 1001 +++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 980 insertions(+), 21 deletions(-)
diff --git a/ntoskrnl/se/token.c b/ntoskrnl/se/token.c
index 2f239e6f8c6..67beb8a8bfc 100644
--- a/ntoskrnl/se/token.c
+++ b/ntoskrnl/se/token.c
@@ -1978,6 +1978,651 @@ Quit:
return Status;
}
+/**
+ * @brief
+ * Private helper function responsible for creating a restricted access
+ * token, that is, a filtered token from privileges and groups and with
+ * restricted SIDs added into the token on demand by the caller.
+ *
+ * @param[in] Token
+ * An existing and valid access token.
+ *
+ * @param[in] PrivilegesToBeDeleted
+ * A list of privileges to be deleted within the token that's going
+ * to be filtered. This parameter is ignored if the caller wants to disable
+ * all the privileges by specifying DISABLE_MAX_PRIVILEGE in the flags
+ * parameter.
+ *
+ * @param[in] SidsToBeDisabled
+ * A list of group SIDs to be disabled within the token. This parameter
+ * can be NULL.
+ *
+ * @param[in] RestrictedSidsIntoToken
+ * A list of restricted SIDs to be added into the token. This parameter
+ * can be NULL.
+ *
+ * @param[in] PrivilegesCount
+ * The privilege count of the privileges list.
+ *
+ * @param[in] RegularGroupsSidCount
+ * The SIDs count of the group SIDs list.
+ *
+ * @param[in] RestrictedSidsCount
+ * The restricted SIDs count of restricted SIDs list.
+ *
+ * @param[in] PrivilegeFlags
+ * Influences how the privileges should be filtered in an access
+ * token. See NtFilterToken syscall for more information.
+ *
+ * @param[in] PreviousMode
+ * Processor level access mode.
+ *
+ * @param[out] FilteredToken
+ * The filtered token, returned to the caller.
+ *
+ * @return
+ * Returns STATUS_SUCCESS if token token filtering has completed successfully.
+ * STATUS_INVALID_PARAMETER is returned if one or more of the parameters
+ * do not meet the conditions imposed by the function. A failure NTSTATUS
+ * code is returned otherwise.
+ *
+ * @remarks
+ * The final outcome of privileges and/or SIDs filtering is not always
+ * deterministic. That is, any privileges or SIDs that aren't present
+ * in the access token are ignored and the function continues with the
+ * next privilege or SID to find for filtering. For a fully deterministic
+ * outcome the caller is responsible for querying the information details
+ * of privileges and SIDs present in the token and then afterwards use
+ * such obtained information to do any kind of filtering to the token.
+ */
+static
+NTSTATUS
+SepPerformTokenFiltering(
+ _In_ PTOKEN Token,
+ _In_opt_ PLUID_AND_ATTRIBUTES PrivilegesToBeDeleted,
+ _In_opt_ PSID_AND_ATTRIBUTES SidsToBeDisabled,
+ _In_opt_ PSID_AND_ATTRIBUTES RestrictedSidsIntoToken,
+ _When_(PrivilegesToBeDeleted != NULL, _In_) ULONG PrivilegesCount,
+ _When_(SidsToBeDisabled != NULL, _In_) ULONG RegularGroupsSidCount,
+ _When_(RestrictedSidsIntoToken != NULL, _In_) ULONG RestrictedSidsCount,
+ _In_ ULONG PrivilegeFlags,
+ _In_ KPROCESSOR_MODE PreviousMode,
+ _Out_ PTOKEN *FilteredToken)
+{
+ PTOKEN AccessToken;
+ NTSTATUS Status;
+ PVOID EndMem;
+ ULONG RestrictedSidsLength;
+ ULONG PrivilegesLength;
+ ULONG PrimaryGroupIndex;
+ ULONG RestrictedSidsInList;
+ ULONG RestrictedSidsInToken;
+ ULONG VariableLength, TotalSize;
+ ULONG PrivsInToken, PrivsInList;
+ ULONG GroupsInToken, GroupsInList;
+ BOOLEAN WantPrivilegesDisabled;
+ BOOLEAN FoundPrivilege;
+ BOOLEAN FoundGroup;
+ PAGED_CODE();
+
+ /* Ensure that the token we get is not garbage */
+ ASSERT(Token);
+
+ /* Assume the caller doesn't want privileges disabled */
+ WantPrivilegesDisabled = FALSE;
+
+ /* Assume we haven't found anything */
+ FoundPrivilege = FALSE;
+ FoundGroup = FALSE;
+
+ /*
+ * Take the size that we need for filtered token
+ * allocation based upon the existing access token
+ * we've been given.
+ */
+ VariableLength = Token->VariableLength;
+
+ if (RestrictedSidsIntoToken != NULL)
+ {
+ /*
+ * If the caller provided a list of restricted SIDs
+ * to be added onto the filtered access token then
+ * we must compute the size which is the total space
+ * of the current token and the length of the restricted
+ * SIDs for the filtered token.
+ */
+ RestrictedSidsLength = RestrictedSidsCount * sizeof(SID_AND_ATTRIBUTES);
+ RestrictedSidsLength += RtlLengthSidAndAttributes(RestrictedSidsCount, RestrictedSidsIntoToken);
+ RestrictedSidsLength = ALIGN_UP_BY(RestrictedSidsLength, sizeof(PVOID));
+
+ /*
+ * The variable length of the token is not just
+ * the actual space length of the existing token
+ * but also the sum of the restricted SIDs length.
+ */
+ VariableLength += RestrictedSidsLength;
+ TotalSize = FIELD_OFFSET(TOKEN, VariablePart) + VariableLength + RestrictedSidsLength;
+ }
+ else
+ {
+ /* Otherwise the size is of the actual current token */
+ TotalSize = FIELD_OFFSET(TOKEN, VariablePart) + VariableLength;
+ }
+
+ /* Set up a filtered token object */
+ Status = ObCreateObject(PreviousMode,
+ SeTokenObjectType,
+ NULL,
+ PreviousMode,
+ NULL,
+ TotalSize,
+ 0,
+ 0,
+ (PVOID*)&AccessToken);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("SepPerformTokenFiltering(): Failed to create the filtered token object (Status 0x%lx)\n", Status);
+ return Status;
+ }
+
+ /* Initialize the token and begin filling stuff to it */
+ RtlZeroMemory(AccessToken, TotalSize);
+
+ /* Set up a lock for the new token */
+ Status = SepCreateTokenLock(AccessToken);
+ if (!NT_SUCCESS(Status))
+ {
+ ObDereferenceObject(AccessToken);
+ return Status;
+ }
+
+ /* Allocate new IDs for the token */
+ ExAllocateLocallyUniqueId(&AccessToken->TokenId);
+ ExAllocateLocallyUniqueId(&AccessToken->ModifiedId);
+
+ /* Copy the type and impersonation level from the token */
+ AccessToken->TokenType = Token->TokenType;
+ AccessToken->ImpersonationLevel = Token->ImpersonationLevel;
+
+ /* Copy the immutable fields */
+ RtlCopyLuid(&AccessToken->TokenSource.SourceIdentifier,
+ &Token->TokenSource.SourceIdentifier);
+ RtlCopyMemory(AccessToken->TokenSource.SourceName,
+ Token->TokenSource.SourceName,
+ sizeof(Token->TokenSource.SourceName));
+
+ RtlCopyLuid(&AccessToken->AuthenticationId, &Token->AuthenticationId);
+ RtlCopyLuid(&AccessToken->ParentTokenId, &Token->TokenId);
+ RtlCopyLuid(&AccessToken->OriginatingLogonSession,
+ &Token->OriginatingLogonSession);
+
+ AccessToken->ExpirationTime = Token->ExpirationTime;
+
+ /* Copy the mutable fields */
+ AccessToken->SessionId = Token->SessionId;
+ AccessToken->TokenFlags = Token->TokenFlags & ~TOKEN_SESSION_NOT_REFERENCED;
+
+ /* Reference the logon session */
+ Status = SepRmReferenceLogonSession(&AccessToken->AuthenticationId);
+ if (!NT_SUCCESS(Status))
+ {
+ /* We failed, bail out*/
+ DPRINT1("SepPerformTokenFiltering(): Failed to reference the logon session (Status 0x%lx)\n", Status);
+ AccessToken->TokenFlags |= TOKEN_SESSION_NOT_REFERENCED;
+ goto Quit;
+ }
+
+ /* Insert the referenced logon session into the token */
+ Status = SepRmInsertLogonSessionIntoToken(AccessToken);
+ if (!NT_SUCCESS(Status))
+ {
+ /* Failed to insert the logon session into the token, bail out */
+ DPRINT1("SepPerformTokenFiltering(): Failed to insert the logon session into token (Status 0x%lx)\n", Status);
+ goto Quit;
+ }
+
+ /* Assign the data that reside in the token's variable information area */
+ AccessToken->VariableLength = VariableLength;
+ EndMem = (PVOID)&AccessToken->VariablePart;
+
+ /* Copy the privileges from the existing token */
+ AccessToken->PrivilegeCount = 0;
+ AccessToken->Privileges = NULL;
+ if (Token->Privileges && (Token->PrivilegeCount > 0))
+ {
+ PrivilegesLength = Token->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES);
+ PrivilegesLength = ALIGN_UP_BY(PrivilegesLength, sizeof(PVOID));
+
+ /*
+ * Ensure that the token can actually hold all
+ * the privileges from the existing token.
+ * Otherwise something's seriously wrong and
+ * we've to guard ourselves.
+ */
+ ASSERT(VariableLength >= PrivilegesLength);
+
+ AccessToken->PrivilegeCount = Token->PrivilegeCount;
+ AccessToken->Privileges = EndMem;
+ EndMem = (PVOID)((ULONG_PTR)EndMem + PrivilegesLength);
+ VariableLength -= PrivilegesLength;
+
+ if (PreviousMode != KernelMode)
+ {
+ _SEH2_TRY
+ {
+ RtlCopyMemory(AccessToken->Privileges,
+ Token->Privileges,
+ AccessToken->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES));
+ }
+ _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+ {
+ Status = _SEH2_GetExceptionCode();
+ _SEH2_YIELD(goto Quit);
+ }
+ _SEH2_END;
+ }
+ else
+ {
+ RtlCopyMemory(AccessToken->Privileges,
+ Token->Privileges,
+ AccessToken->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES));
+ }
+ }
+
+ /* Copy the user and groups */
+ AccessToken->UserAndGroupCount = 0;
+ AccessToken->UserAndGroups = NULL;
+ if (Token->UserAndGroups && (Token->UserAndGroupCount > 0))
+ {
+ AccessToken->UserAndGroupCount = Token->UserAndGroupCount;
+ AccessToken->UserAndGroups = EndMem;
+ EndMem = &AccessToken->UserAndGroups[AccessToken->UserAndGroupCount];
+ VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->UserAndGroups);
+
+ if (PreviousMode != KernelMode)
+ {
+ _SEH2_TRY
+ {
+ Status = RtlCopySidAndAttributesArray(AccessToken->UserAndGroupCount,
+ Token->UserAndGroups,
+ VariableLength,
+ AccessToken->UserAndGroups,
+ EndMem,
+ &EndMem,
+ &VariableLength);
+ }
+ _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+ {
+ Status = _SEH2_GetExceptionCode();
+ _SEH2_YIELD(goto Quit);
+ }
+ _SEH2_END;
+ }
+ else
+ {
+ Status = RtlCopySidAndAttributesArray(AccessToken->UserAndGroupCount,
+ Token->UserAndGroups,
+ VariableLength,
+ AccessToken->UserAndGroups,
+ EndMem,
+ &EndMem,
+ &VariableLength);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("SepPerformTokenFiltering(): Failed to copy the groups into token (Status 0x%lx)\n", Status);
+ goto Quit;
+ }
+ }
+ }
+
+ /* Copy the restricted SIDs */
+ AccessToken->RestrictedSidCount = 0;
+ AccessToken->RestrictedSids = NULL;
+ if (Token->RestrictedSids && (Token->RestrictedSidCount > 0))
+ {
+ AccessToken->RestrictedSidCount = Token->RestrictedSidCount;
+ AccessToken->RestrictedSids = EndMem;
+ EndMem = &AccessToken->RestrictedSids[AccessToken->RestrictedSidCount];
+ VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->RestrictedSids);
+
+ if (PreviousMode != KernelMode)
+ {
+ _SEH2_TRY
+ {
+ Status = RtlCopySidAndAttributesArray(AccessToken->RestrictedSidCount,
+ Token->RestrictedSids,
+ VariableLength,
+ AccessToken->RestrictedSids,
+ EndMem,
+ &EndMem,
+ &VariableLength);
+ }
+ _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+ {
+ Status = _SEH2_GetExceptionCode();
+ _SEH2_YIELD(goto Quit);
+ }
+ _SEH2_END;
+ }
+ else
+ {
+ Status = RtlCopySidAndAttributesArray(AccessToken->RestrictedSidCount,
+ Token->RestrictedSids,
+ VariableLength,
+ AccessToken->RestrictedSids,
+ EndMem,
+ &EndMem,
+ &VariableLength);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("SepPerformTokenFiltering(): Failed to copy the restricted SIDs into token (Status 0x%lx)\n", Status);
+ goto Quit;
+ }
+ }
+ }
+
+ /* Search for the primary group */
+ Status = SepFindPrimaryGroupAndDefaultOwner(AccessToken,
+ Token->PrimaryGroup,
+ NULL,
+ &PrimaryGroupIndex,
+ NULL);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("SepPerformTokenFiltering(): Failed searching for the primary group (Status 0x%lx)\n", Status);
+ goto Quit;
+ }
+
+ /* Assign the primary group and default owner index now */
+ AccessToken->PrimaryGroup = AccessToken->UserAndGroups[PrimaryGroupIndex].Sid;
+ AccessToken->DefaultOwnerIndex = Token->DefaultOwnerIndex;
+
+ /* Now allocate the token's dynamic information area and set the data */
+ AccessToken->DynamicAvailable = 0;
+ AccessToken->DynamicPart = NULL;
+ if (Token->DynamicPart && Token->DefaultDacl)
+ {
+ AccessToken->DynamicPart = ExAllocatePoolWithTag(PagedPool,
+ Token->DefaultDacl->AclSize,
+ TAG_TOKEN_DYNAMIC);
+ if (AccessToken->DynamicPart == NULL)
+ {
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ goto Quit;
+ }
+
+ EndMem = (PVOID)AccessToken->DynamicPart;
+ AccessToken->DefaultDacl = EndMem;
+
+ RtlCopyMemory(AccessToken->DefaultDacl,
+ Token->DefaultDacl,
+ Token->DefaultDacl->AclSize);
+ }
+
+ /*
+ * Now figure out what does the caller
+ * want with the privileges.
+ */
+ if (PrivilegeFlags & DISABLE_MAX_PRIVILEGE)
+ {
+ /*
+ * The caller wants them disabled, cache this request
+ * for later operations.
+ */
+ WantPrivilegesDisabled = TRUE;
+ }
+
+ if (PrivilegeFlags & SANDBOX_INERT)
+ {
+ /* The caller wants an inert token, store the TOKEN_SANDBOX_INERT flag now */
+ AccessToken->TokenFlags |= TOKEN_SANDBOX_INERT;
+ }
+
+ /*
+ * Now it's time to filter the token's privileges.
+ * Loop all the privileges in the token.
+ */
+ for (PrivsInToken = 0; PrivsInToken < AccessToken->PrivilegeCount; PrivsInToken++)
+ {
+ if (WantPrivilegesDisabled)
+ {
+ /*
+ * We got the acknowledgement that the caller wants
+ * to disable all the privileges so let's just do it.
+ * However, as per the general documentation is stated
+ * that only SE_CHANGE_NOTIFY_PRIVILEGE must be kept
+ * therefore in that case we must skip this privilege.
+ */
+ if (AccessToken->Privileges[PrivsInToken].Luid.LowPart == SE_CHANGE_NOTIFY_PRIVILEGE)
+ {
+ continue;
+ }
+ else
+ {
+ /*
+ * The act of disabling privileges actually means
+ * "deleting" them from the access token entirely.
+ * First we must disable them so that we can update
+ * token flags accordingly.
+ */
+ AccessToken->Privileges[PrivsInToken].Attributes &= ~SE_PRIVILEGE_ENABLED;
+ SepUpdateSinglePrivilegeFlagToken(AccessToken, PrivsInToken);
+
+ /* Remove the privileges now */
+ SepRemovePrivilegeToken(AccessToken, PrivsInToken);
+ PrivsInToken--;
+ }
+ }
+ else
+ {
+ if (PrivilegesToBeDeleted != NULL)
+ {
+ /* Loop the privileges we've got to delete */
+ for (PrivsInList = 0; PrivsInList < PrivilegesCount; PrivsInList++)
+ {
+ /* Does this privilege exist in the token? */
+ if (RtlEqualLuid(&AccessToken->Privileges[PrivsInToken].Luid,
+ &PrivilegesToBeDeleted[PrivsInList].Luid))
+ {
+ /* Mark that we found it */
+ FoundPrivilege = TRUE;
+ break;
+ }
+ }
+
+ /* Did we find the privilege? */
+ if (PrivsInList == PrivilegesCount)
+ {
+ /* We didn't, continue with next one */
+ continue;
+ }
+ }
+ }
+
+ /*
+ * If we have found the target privilege in the token
+ * based on the privileges list given by the caller
+ * then begin deleting it.
+ */
+ if (FoundPrivilege)
+ {
+ /* Disable the privilege and update the flags */
+ AccessToken->Privileges[PrivsInToken].Attributes &= ~SE_PRIVILEGE_ENABLED;
+ SepUpdateSinglePrivilegeFlagToken(AccessToken, PrivsInToken);
+
+ /* Delete the privilege */
+ SepRemovePrivilegeToken(AccessToken, PrivsInToken);
+
+ /*
+ * Adjust the index and reset the FoundPrivilege indicator
+ * so that we can continue with the next privilege to delete.
+ */
+ PrivsInToken--;
+ FoundPrivilege = FALSE;
+ continue;
+ }
+ }
+
+ /*
+ * Loop the group SIDs that we want to disable as
+ * per on the request by the caller.
+ */
+ if (SidsToBeDisabled != NULL)
+ {
+ for (GroupsInToken = 0; GroupsInToken < AccessToken->UserAndGroupCount; GroupsInToken++)
+ {
+ for (GroupsInList = 0; GroupsInList < RegularGroupsSidCount; GroupsInList++)
+ {
+ /* Does this group SID exist in the token? */
+ if (RtlEqualSid(&AccessToken->UserAndGroups[GroupsInToken].Sid,
+ &SidsToBeDisabled[GroupsInList].Sid))
+ {
+ /* Mark that we found it */
+ FoundGroup = TRUE;
+ break;
+ }
+ }
+
+ /* Did we find the group? */
+ if (GroupsInList == RegularGroupsSidCount)
+ {
+ /* We didn't, continue with next one */
+ continue;
+ }
+
+ /* If we have found the group, disable it */
+ if (FoundGroup)
+ {
+ /*
+ * If the acess token belongs to the administrators
+ * group and this is the target group, we must take
+ * away TOKEN_HAS_ADMIN_GROUP flag from the token.
+ */
+ if (RtlEqualSid(SeAliasAdminsSid,
+ &AccessToken->UserAndGroups[GroupsInToken].Sid))
+ {
+ AccessToken->TokenFlags &= ~TOKEN_HAS_ADMIN_GROUP;
+ }
+
+ /*
+ * If the target group that we have found it is the
+ * owner then from now on it no longer is but the user.
+ * Therefore assign the default owner index as the user.
+ */
+ if (AccessToken->DefaultOwnerIndex == GroupsInToken)
+ {
+ AccessToken->DefaultOwnerIndex = 0;
+ }
+
+ /*
+ * The principle of disabling a group SID is by
+ * taking away SE_GROUP_ENABLED_BY_DEFAULT and
+ * SE_GROUP_ENABLED attributes and assign
+ * SE_GROUP_USE_FOR_DENY_ONLY. This renders
+ * SID a "Deny only" SID.
+ */
+ AccessToken->UserAndGroups[GroupsInToken].Attributes &= ~(SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT);
+ AccessToken->UserAndGroups[GroupsInToken].Attributes |= SE_GROUP_USE_FOR_DENY_ONLY;
+
+ /* Adjust the index and continue with the next group */
+ GroupsInToken--;
+ FoundGroup = FALSE;
+ continue;
+ }
+ }
+ }
+
+ /*
+ * Insert the restricted SIDs into the token on
+ * the request by the caller.
+ */
+ if (RestrictedSidsIntoToken != NULL)
+ {
+ for (RestrictedSidsInList = 0; RestrictedSidsInList < RestrictedSidsCount; RestrictedSidsInList++)
+ {
+ /* Did the caller assign attributes to the restricted SIDs? */
+ if (RestrictedSidsIntoToken[RestrictedSidsInList].Attributes != 0)
+ {
+ /* There mustn't be any attributes, bail out */
+ DPRINT1("SepPerformTokenFiltering(): There mustn't be any attributes to restricted SIDs!\n");
+ Status = STATUS_INVALID_PARAMETER;
+ goto Quit;
+ }
+ }
+
+ /*
+ * Ensure that the token can hold the restricted SIDs
+ * (the variable length is calculated at the beginning
+ * of the routine call).
+ */
+ ASSERT(VariableLength >= RestrictedSidsLength);
+
+ /*
+ * Now let's begin inserting the restricted SIDs into the filtered
+ * access token from the list the caller gave us.
+ */
+ AccessToken->RestrictedSidCount = RestrictedSidsCount;
+ AccessToken->RestrictedSids = EndMem;
+ EndMem = (PVOID)((ULONG_PTR)EndMem + RestrictedSidsLength);
+ VariableLength -= RestrictedSidsLength;
+
+ if (PreviousMode != KernelMode)
+ {
+ _SEH2_TRY
+ {
+ RtlCopyMemory(AccessToken->RestrictedSids,
+ RestrictedSidsIntoToken,
+ AccessToken->RestrictedSidCount * sizeof(SID_AND_ATTRIBUTES));
+ }
+ _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+ {
+ Status = _SEH2_GetExceptionCode();
+ _SEH2_YIELD(goto Quit);
+ }
+ _SEH2_END;
+ }
+ else
+ {
+ RtlCopyMemory(AccessToken->RestrictedSids,
+ RestrictedSidsIntoToken,
+ AccessToken->RestrictedSidCount * sizeof(SID_AND_ATTRIBUTES));
+ }
+
+ /*
+ * As we've copied the restricted SIDs into
+ * the token, we must assign them the following
+ * combination of attributes SE_GROUP_ENABLED,
+ * SE_GROUP_ENABLED_BY_DEFAULT and SE_GROUP_MANDATORY.
+ * With such attributes we estabilish that restricting
+ * SIDs into the token are enabled for access checks.
+ */
+ for (RestrictedSidsInToken = 0; RestrictedSidsInToken < AccessToken->RestrictedSidCount; RestrictedSidsInToken++)
+ {
+ AccessToken->RestrictedSids[RestrictedSidsInToken].Attributes |= (SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_MANDATORY);
+ }
+
+ /*
+ * As we added restricted SIDs into the token, mark
+ * it as restricted.
+ */
+ AccessToken->TokenFlags |= TOKEN_IS_RESTRICTED;
+ }
+
+ /* We've finally filtered the token, give it to the caller */
+ *FilteredToken = AccessToken;
+ Status = STATUS_SUCCESS;
+ DPRINT("SepPerformTokenFiltering(): The token has been filtered!\n");
+
+Quit:
+ if (!NT_SUCCESS(Status))
+ {
+ /* Dereference the token */
+ ObDereferenceObject(AccessToken);
+ }
+
+ return Status;
+}
+
/**
* @brief
* Creates the system process token.
@@ -2259,7 +2904,6 @@ SepCreateSystemAnonymousLogonTokenNoEveryone(VOID)
/* PUBLIC FUNCTIONS ***********************************************************/
/**
- * @unimplemented
* @brief
* Filters an access token from an existing token, making it more restricted
* than the previous one.
@@ -2269,22 +2913,34 @@ SepCreateSystemAnonymousLogonTokenNoEveryone(VOID)
*
* @param[in] Flags
* Privilege flag options. This parameter argument influences how the token
- * is filtered. Such parameter can be 0.
+ * is filtered. Such parameter can be 0. See NtFilterToken syscall for
+ * more information.
*
* @param[in] SidsToDisable
- * Array of SIDs to disable.
+ * Array of SIDs to disable. Such parameter can be NULL.
*
* @param[in] PrivilegesToDelete
- * Array of privileges to delete.
+ * Array of privileges to delete. If DISABLE_MAX_PRIVILEGE flag is specified
+ * in the Flags parameter, PrivilegesToDelete is ignored.
*
* @param[in] RestrictedSids
- * An array of restricted SIDs for the new filtered token.
+ * An array of restricted SIDs for the new filtered token. Such parameter
+ * can be NULL.
*
* @param[out] FilteredToken
* The newly filtered token, returned to the caller.
*
* @return
- * To be added...
+ * Returns STATUS_SUCCESS if the function has successfully completed its
+ * operations and that the access token has been filtered. STATUS_INVALID_PARAMETER
+ * is returned if one or more of the parameter are not valid. A failure NTSTATUS code
+ * is returned otherwise.
+ *
+ * @remarks
+ * WARNING -- The caller IS RESPONSIBLE for locking the existing access token
+ * before attempting to do any kind of filtering operation into
+ * the token. The lock MUST BE RELEASED after this kernel routine
+ * has finished doing its work.
*/
NTSTATUS
NTAPI
@@ -2294,10 +2950,64 @@ SeFilterToken(
_In_opt_ PTOKEN_GROUPS SidsToDisable,
_In_opt_ PTOKEN_PRIVILEGES PrivilegesToDelete,
_In_opt_ PTOKEN_GROUPS RestrictedSids,
- _Out_ PACCESS_TOKEN * FilteredToken)
+ _Out_ PACCESS_TOKEN *FilteredToken)
{
- UNIMPLEMENTED;
- return STATUS_NOT_IMPLEMENTED;
+ NTSTATUS Status;
+ PTOKEN AccessToken;
+ ULONG PrivilegesCount = 0;
+ ULONG SidsCount = 0;
+ ULONG RestrictedSidsCount = 0;
+ PAGED_CODE();
+
+ /* Begin copying the counters */
+ if (SidsToDisable != NULL)
+ {
+ SidsCount = SidsToDisable->GroupCount;
+ }
+
+ if (PrivilegesToDelete != NULL)
+ {
+ PrivilegesCount = PrivilegesToDelete->PrivilegeCount;
+ }
+
+ if (RestrictedSids != NULL)
+ {
+ RestrictedSidsCount = RestrictedSids->GroupCount;
+ }
+
+ /* Call the internal API */
+ Status = SepPerformTokenFiltering(ExistingToken,
+ PrivilegesToDelete->Privileges,
+ SidsToDisable->Groups,
+ RestrictedSids->Groups,
+ PrivilegesCount,
+ SidsCount,
+ RestrictedSidsCount,
+ Flags,
+ KernelMode,
+ &AccessToken);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("SeFilterToken(): Failed to filter the token (Status 0x%lx)\n", Status);
+ return Status;
+ }
+
+ /* Insert the filtered token */
+ Status = ObInsertObject(AccessToken,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ NULL);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("SeFilterToken(): Failed to insert the token (Status 0x%lx)\n", Status);
+ return Status;
+ }
+
+ /* Give it to the caller */
+ *FilteredToken = AccessToken;
+ return Status;
}
/**
@@ -5843,31 +6553,67 @@ NtCompareTokens(
}
/**
- * @unimplemented
* @brief
- * Opens a token that is tied to a thread handle.
+ * Creates an access token in a restricted form
+ * from the original existing token, that is, such
+ * action is called filtering.
*
* @param[in] ExistingTokenHandle
- * An existing token for filtering.
+ * A handle to an access token which is to be filtered.
*
* @param[in] Flags
- * Privilege flag options. This parameter argument influences how the token
- * is filtered. Such parameter can be 0.
+ * Privilege flag options. This parameter argument influences how the
+ * token's privileges are filtered. For further details see remarks.
*
* @param[in] SidsToDisable
- * Array of SIDs to disable.
+ * Array of SIDs to disable. The action of doing so assigns the
+ * SE_GROUP_USE_FOR_DENY_ONLY attribute to the respective group
+ * SID and takes away SE_GROUP_ENABLED and SE_GROUP_ENABLED_BY_DEFAULT.
+ * This parameter can be NULL. This can be a UM pointer.
*
* @param[in] PrivilegesToDelete
- * Array of privileges to delete.
+ * Array of privileges to delete. The function will walk within this
+ * array to determine if the specified privileges do exist in the
+ * access token. Any missing privileges gets ignored. This parameter
+ * can be NULL. This can be a UM pointer.
*
* @param[in] RestrictedSids
- * An array of restricted SIDs for the new filtered token.
+ * An array list of restricted groups SID to be added in the access
+ * token. A token that is already restricted the newly added restricted
+ * SIDs are redundant information in addition to the existing restricted
+ * SIDs in the token. This parameter can be NULL. This can be a UM pointer.
*
* @param[out] NewTokenHandle
- * The newly filtered token, returned to the caller.
+ * A new handle to the restricted (filtered) access token. This can be a
+ * UM pointer.
*
* @return
- * To be added...
+ * Returns STATUS_SUCCESS if the routine has successfully filtered the
+ * access token. STATUS_INVALID_PARAMETER is returned if one or more
+ * parameters are not valid (see SepPerformTokenFiltering routine call
+ * for more information). A failure NTSTATUS code is returned otherwise.
+ *
+ * @remarks
+ * The Flags parameter determines the final outcome of how the privileges
+ * in an access token are filtered. This parameter can take these supported
+ * values (these can be combined):
+ *
+ * 0 -- Filter the token's privileges in the usual way. The function expects
+ * that the caller MUST PROVIDE a valid array list of privileges to be
+ * deleted (that is, PrivilegesToDelete MUSTN'T BE NULL).
+ *
+ * DISABLE_MAX_PRIVILEGE -- Disables (deletes) all the privileges except SeChangeNotifyPrivilege
+ * in the new access token. Bear in mind if this flag is specified
+ * the routine ignores PrivilegesToDelete.
+ *
+ * SANDBOX_INERT -- Stores the TOKEN_SANDBOX_INERT token flag within the access token.
+ *
+ * LUA_TOKEN -- The newly filtered access token is a LUA token. This flag is not
+ * supported in Windows Server 2003.
+ *
+ * WRITE_RESTRICTED -- The newly filtered token has the restricted SIDs that are
+ * considered only when evaluating write access onto the token.
+ * This value is not supported in Windows Server 2003.
*/
NTSTATUS
NTAPI
@@ -5879,8 +6625,221 @@ NtFilterToken(
_In_opt_ PTOKEN_GROUPS RestrictedSids,
_Out_ PHANDLE NewTokenHandle)
{
- UNIMPLEMENTED;
- return STATUS_NOT_IMPLEMENTED;
+ PTOKEN Token, FilteredToken;
+ HANDLE FilteredTokenHandle;
+ NTSTATUS Status;
+ KPROCESSOR_MODE PreviousMode;
+ OBJECT_HANDLE_INFORMATION HandleInfo;
+ ULONG ResultLength;
+ ULONG CapturedSidsCount = 0;
+ ULONG CapturedPrivilegesCount = 0;
+ ULONG CapturedRestrictedSidsCount = 0;
+ ULONG ProbeSize = 0;
+ PSID_AND_ATTRIBUTES CapturedSids = NULL;
+ PSID_AND_ATTRIBUTES CapturedRestrictedSids = NULL;
+ PLUID_AND_ATTRIBUTES CapturedPrivileges = NULL;
+
+ PAGED_CODE();
+
+ PreviousMode = ExGetPreviousMode();
+
+ _SEH2_TRY
+ {
+ /* Probe SidsToDisable */
+ if (SidsToDisable != NULL)
+ {
+ /* Probe the header */
+ ProbeForRead(SidsToDisable, sizeof(*SidsToDisable), sizeof(ULONG));
+
+ CapturedSidsCount = SidsToDisable->GroupCount;
+ ProbeSize = FIELD_OFFSET(TOKEN_GROUPS, Groups[CapturedSidsCount]);
+
+ ProbeForRead(SidsToDisable, ProbeSize, sizeof(ULONG));
+ }
+
+ /* Probe PrivilegesToDelete */
+ if (PrivilegesToDelete != NULL)
+ {
+ /* Probe the header */
+ ProbeForRead(PrivilegesToDelete, sizeof(*PrivilegesToDelete), sizeof(ULONG));
+
+ CapturedPrivilegesCount = PrivilegesToDelete->PrivilegeCount;
+ ProbeSize = FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges[CapturedPrivilegesCount]);
+
+ ProbeForRead(PrivilegesToDelete, ProbeSize, sizeof(ULONG));
+ }
+
+ /* Probe RestrictedSids */
+ if (RestrictedSids != NULL)
+ {
+ /* Probe the header */
+ ProbeForRead(RestrictedSids, sizeof(*RestrictedSids), sizeof(ULONG));
+
+ CapturedRestrictedSidsCount = RestrictedSids->GroupCount;
+ ProbeSize = FIELD_OFFSET(TOKEN_GROUPS, Groups[CapturedRestrictedSidsCount]);
+
+ ProbeForRead(RestrictedSids, ProbeSize, sizeof(ULONG));
+ }
+
+ /* Probe the handle */
+ ProbeForWriteHandle(NewTokenHandle);
+ }
+ _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+ {
+ /* Return the exception code */
+ _SEH2_YIELD(return _SEH2_GetExceptionCode());
+ }
+ _SEH2_END;
+
+ /* Reference the token and do the job */
+ Status = ObReferenceObjectByHandle(ExistingTokenHandle,
+ TOKEN_DUPLICATE,
+ SeTokenObjectType,
+ PreviousMode,
+ (PVOID*)&Token,
+ &HandleInfo);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("NtFilterToken(): Failed to reference the token (Status 0x%lx)\n", Status);
+ return Status;
+ }
+
+ /* Lock the token */
+ SepAcquireTokenLockExclusive(Token);
+
+ /* Capture the group SIDs */
+ if (SidsToDisable != NULL)
+ {
+ Status = SeCaptureSidAndAttributesArray(SidsToDisable->Groups,
+ CapturedSidsCount,
+ PreviousMode,
+ NULL,
+ 0,
+ PagedPool,
+ TRUE,
+ &CapturedSids,
+ &ResultLength);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("NtFilterToken(): Failed to capture the SIDs (Status 0x%lx)\n", Status);
+ goto Quit;
+ }
+ }
+
+ /* Capture the privileges */
+ if (PrivilegesToDelete != NULL)
+ {
+ Status = SeCaptureLuidAndAttributesArray(PrivilegesToDelete->Privileges,
+ CapturedPrivilegesCount,
+ PreviousMode,
+ NULL,
+ 0,
+ PagedPool,
+ TRUE,
+ &CapturedPrivileges,
+ &ResultLength);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("NtFilterToken(): Failed to capture the privileges (Status 0x%lx)\n", Status);
+ goto Quit;
+ }
+ }
+
+ /* Capture the restricted SIDs */
+ if (RestrictedSids != NULL)
+ {
+ Status = SeCaptureSidAndAttributesArray(RestrictedSids->Groups,
+ CapturedRestrictedSidsCount,
+ PreviousMode,
+ NULL,
+ 0,
+ PagedPool,
+ TRUE,
+ &CapturedRestrictedSids,
+ &ResultLength);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("NtFilterToken(): Failed to capture the restricted SIDs (Status 0x%lx)\n", Status);
+ goto Quit;
+ }
+ }
+
+ /* Call the internal API so that it can filter the token for us */
+ Status = SepPerformTokenFiltering(Token,
+ CapturedPrivileges,
+ CapturedSids,
+ CapturedRestrictedSids,
+ CapturedPrivilegesCount,
+ CapturedSidsCount,
+ CapturedRestrictedSidsCount,
+ Flags,
+ PreviousMode,
+ &FilteredToken);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("NtFilterToken(): Failed to filter the token (Status 0x%lx)\n", Status);
+ goto Quit;
+ }
+
+ /* We got our filtered token, insert it to the handle */
+ Status = ObInsertObject(FilteredToken,
+ NULL,
+ HandleInfo.GrantedAccess,
+ 0,
+ NULL,
+ &FilteredTokenHandle);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("NtFilterToken(): Failed to insert the filtered token object into the handle (Status 0x%lx)\n", Status);
+ goto Quit;
+ }
+
+ /* And give it to the caller once we're done */
+ _SEH2_TRY
+ {
+ *NewTokenHandle = FilteredTokenHandle;
+ }
+ _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+ {
+ Status = _SEH2_GetExceptionCode();
+ _SEH2_YIELD(goto Quit);
+ }
+ _SEH2_END;
+
+Quit:
+ /* Unlock and dereference the token */
+ SepReleaseTokenLock(Token);
+ ObDereferenceObject(Token);
+
+ /* Release all the stuff we've captured */
+ if (CapturedSids != NULL)
+ {
+ SeReleaseSidAndAttributesArray(CapturedSids,
+ PreviousMode,
+ TRUE);
+
+ CapturedSids = NULL;
+ }
+
+ if (CapturedPrivileges != NULL)
+ {
+ SeReleaseLuidAndAttributesArray(CapturedPrivileges,
+ PreviousMode,
+ TRUE);
+
+ CapturedPrivileges = NULL;
+ }
+
+ if (CapturedRestrictedSids != NULL)
+ {
+ SeReleaseSidAndAttributesArray(CapturedRestrictedSids,
+ PreviousMode,
+ TRUE);
+
+ CapturedRestrictedSids = NULL;
+ }
+
+ return Status;
}
/**