reactos/sdk/lib/rtl/priv.c
Timo Kreuzer 49da1cdbeb [NTDLL] Fix some vista exports
Make sure RtlRemovePrivileges gets compiled, when DLL_EXPORT_VERSION is 0x600+
Enable all existing functions from rtl_vista.
Hack CsrNewThread export to exist on later versions, too
2023-08-11 19:00:42 +03:00

590 lines
19 KiB
C

/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS system libraries
* FILE: lib/rtl/priv.c
* PURPOSE: Security related functions and Security Objects
* PROGRAMMER: Eric Kohl
* Pierre Schweitzer (pierre@reactos.org)
*/
/* INCLUDES *****************************************************************/
#include <rtl.h>
#define NDEBUG
#include <debug.h>
/* FUNCTIONS ***************************************************************/
/*
* @implemented
*/
NTSTATUS
NTAPI
RtlpOpenThreadToken(IN ACCESS_MASK DesiredAccess,
OUT PHANDLE TokenHandle)
{
NTSTATUS Status;
Status = ZwOpenThreadToken(NtCurrentThread(), DesiredAccess,
TRUE, TokenHandle);
if (!NT_SUCCESS(Status))
{
Status = ZwOpenThreadToken(NtCurrentThread(), DesiredAccess,
FALSE, TokenHandle);
}
return Status;
}
/*
* @implemented
*/
NTSTATUS
NTAPI
RtlImpersonateSelf(IN SECURITY_IMPERSONATION_LEVEL ImpersonationLevel)
{
HANDLE ProcessToken;
HANDLE ImpersonationToken;
NTSTATUS Status;
OBJECT_ATTRIBUTES ObjAttr;
SECURITY_QUALITY_OF_SERVICE Sqos;
PAGED_CODE_RTL();
Status = ZwOpenProcessToken(NtCurrentProcess(),
TOKEN_DUPLICATE,
&ProcessToken);
if (!NT_SUCCESS(Status))
{
DPRINT1("NtOpenProcessToken() failed (Status %lx)\n", Status);
return Status;
}
Sqos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE);
Sqos.ImpersonationLevel = ImpersonationLevel;
Sqos.ContextTrackingMode = 0;
Sqos.EffectiveOnly = FALSE;
InitializeObjectAttributes(&ObjAttr,
NULL,
0,
NULL,
NULL);
ObjAttr.SecurityQualityOfService = &Sqos;
Status = ZwDuplicateToken(ProcessToken,
TOKEN_IMPERSONATE,
&ObjAttr,
Sqos.EffectiveOnly, /* why both here _and_ in Sqos? */
TokenImpersonation,
&ImpersonationToken);
if (!NT_SUCCESS(Status))
{
DPRINT1("NtDuplicateToken() failed (Status %lx)\n", Status);
NtClose(ProcessToken);
return Status;
}
Status = ZwSetInformationThread(NtCurrentThread(),
ThreadImpersonationToken,
&ImpersonationToken,
sizeof(HANDLE));
if (!NT_SUCCESS(Status))
{
DPRINT1("NtSetInformationThread() failed (Status %lx)\n", Status);
}
ZwClose(ImpersonationToken);
ZwClose(ProcessToken);
return Status;
}
/*
* @implemented
*/
NTSTATUS
NTAPI
RtlAcquirePrivilege(IN PULONG Privilege,
IN ULONG NumPriv,
IN ULONG Flags,
OUT PVOID *ReturnedState)
{
PRTL_ACQUIRE_STATE State;
NTSTATUS Status, IntStatus;
ULONG ReturnLength, i, OldSize;
SECURITY_QUALITY_OF_SERVICE Sqos;
OBJECT_ATTRIBUTES ObjectAttributes;
HANDLE ImpersonationToken = 0, ProcessToken;
DPRINT("RtlAcquirePrivilege(%p, %u, %u, %p)\n", Privilege, NumPriv, Flags, ReturnedState);
/* Validate flags */
if (Flags & ~(RTL_ACQUIRE_PRIVILEGE_PROCESS | RTL_ACQUIRE_PRIVILEGE_IMPERSONATE))
{
return STATUS_INVALID_PARAMETER;
}
/* If user wants to acquire privileges for the process, we have to impersonate him */
if (Flags & RTL_ACQUIRE_PRIVILEGE_PROCESS)
{
Flags |= RTL_ACQUIRE_PRIVILEGE_IMPERSONATE;
}
/* Allocate enough memory to hold: old privileges (fixed buffer size, might not be enough)
* new privileges (big enough, after old privileges memory area)
*/
State = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(RTL_ACQUIRE_STATE) + sizeof(TOKEN_PRIVILEGES) +
(NumPriv - ANYSIZE_ARRAY) * sizeof(LUID_AND_ATTRIBUTES));
if (!State)
{
return STATUS_NO_MEMORY;
}
/* Only zero a bit of the memory (will be faster that way) */
State->Token = 0;
State->OldImpersonationToken = 0;
State->Flags = 0;
State->OldPrivileges = NULL;
/* Check whether we have already an active impersonation */
if (NtCurrentTeb()->IsImpersonating)
{
/* Check whether we want to impersonate */
if (Flags & RTL_ACQUIRE_PRIVILEGE_IMPERSONATE)
{
/* That's all fine, just get the token.
* We need access for: adjust (obvious...) but also
* query, to be able to query old privileges
*/
Status = RtlpOpenThreadToken(TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &State->Token);
if (!NT_SUCCESS(Status))
{
RtlFreeHeap(RtlGetProcessHeap(), 0, State);
return Status;
}
}
else
{
/* Otherwise, we have to temporary disable active impersonation.
* Get previous impersonation token to save it
*/
Status = RtlpOpenThreadToken(TOKEN_IMPERSONATE, &State->OldImpersonationToken);
if (!NT_SUCCESS(Status))
{
RtlFreeHeap(RtlGetProcessHeap(), 0, State);
return Status;
}
/* Remember the fact we had an active impersonation */
State->Flags |= RTL_ACQUIRE_PRIVILEGE_IMPERSONATE;
/* Revert impersonation (ie, give 0 as handle) */
Status = ZwSetInformationThread(NtCurrentThread(),
ThreadImpersonationToken,
&ImpersonationToken,
sizeof(HANDLE));
}
}
/* If we have no token yet (which is likely) */
if (!State->Token)
{
/* If we are asked to use process, then do */
if (Flags & RTL_ACQUIRE_PRIVILEGE_PROCESS)
{
Status = ZwOpenProcessToken(NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&State->Token);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
}
else
{
/* Otherwise, we have to impersonate.
* Open token for duplication
*/
Status = ZwOpenProcessToken(NtCurrentProcess(), TOKEN_DUPLICATE, &ProcessToken);
InitializeObjectAttributes(&ObjectAttributes,
NULL,
0,
NULL,
NULL);
ObjectAttributes.SecurityQualityOfService = &Sqos;
Sqos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE);
Sqos.ImpersonationLevel = SecurityDelegation;
Sqos.ContextTrackingMode = 1;
Sqos.EffectiveOnly = FALSE;
/* Duplicate */
Status = ZwDuplicateToken(ProcessToken,
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_IMPERSONATE,
&ObjectAttributes,
FALSE,
TokenImpersonation,
&ImpersonationToken);
if (!NT_SUCCESS(Status))
{
ZwClose(ProcessToken);
goto Cleanup;
}
/* Assign our duplicated token to current thread */
Status = ZwSetInformationThread(NtCurrentThread(),
ThreadImpersonationToken,
&ImpersonationToken,
sizeof(HANDLE));
if (!NT_SUCCESS(Status))
{
ZwClose(ImpersonationToken);
ZwClose(ProcessToken);
goto Cleanup;
}
/* Save said token and the fact we have impersonated */
State->Token = ImpersonationToken;
State->Flags |= RTL_ACQUIRE_PRIVILEGE_IMPERSONATE;
ZwClose(ProcessToken);
}
}
/* Properly set the privileges pointers:
* OldPrivileges points to the static memory in struct (= OldPrivBuffer)
* NewPrivileges points to the dynamic memory after OldPrivBuffer
* There's NO overflow risks (OldPrivileges is always used with its size)
*/
State->OldPrivileges = (PTOKEN_PRIVILEGES)State->OldPrivBuffer;
State->NewPrivileges = (PTOKEN_PRIVILEGES)(State->OldPrivBuffer + (sizeof(State->OldPrivBuffer) / sizeof(State->OldPrivBuffer[0])));
/* Assign all the privileges to be acquired */
State->NewPrivileges->PrivilegeCount = NumPriv;
for (i = 0; i < NumPriv; ++i)
{
State->NewPrivileges->Privileges[i].Luid.LowPart = Privilege[i];
State->NewPrivileges->Privileges[i].Luid.HighPart = 0;
State->NewPrivileges->Privileges[i].Attributes = SE_PRIVILEGE_ENABLED;
}
/* Start privileges adjustements */
OldSize = sizeof(State->OldPrivBuffer);
do
{
ReturnLength = sizeof(State->OldPrivBuffer);
Status = ZwAdjustPrivilegesToken(State->Token, FALSE, State->NewPrivileges,
OldSize, State->OldPrivileges, &ReturnLength);
/* This is returned when OldPrivileges buffer is too small */
if (Status == STATUS_BUFFER_TOO_SMALL)
{
/* Try to allocate a new one, big enough to hold data */
State->OldPrivileges = RtlAllocateHeap(RtlGetProcessHeap(), 0, ReturnLength);
if (State->OldPrivileges)
{
DPRINT("Allocated old privileges: %p\n", State->OldPrivileges);
OldSize = ReturnLength;
continue;
}
else
{
/* If we failed, properly set status: we failed because of the lack of memory */
Status = STATUS_NO_MEMORY;
}
}
/* If we failed to assign at least one privilege */
if (Status == STATUS_NOT_ALL_ASSIGNED)
{
/* If there was actually only one privilege to acquire, use more accurate status */
if (NumPriv == 1)
{
Status = STATUS_PRIVILEGE_NOT_HELD;
}
}
/* Fail if needed, otherwise return our state to caller */
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
else
{
*ReturnedState = State;
break;
}
} while (TRUE);
DPRINT("RtlAcquirePrivilege succeed!\n");
return Status;
Cleanup:
/* If we allocated our own buffer for old privileges, release it */
if (State->OldPrivileges && (PVOID)State->OldPrivBuffer != (PVOID)State->OldPrivileges)
{
RtlFreeHeap(RtlGetProcessHeap(), 0, State->OldPrivileges);
}
/* Do we have to restore previously active impersonation? */
if (State->Flags & RTL_ACQUIRE_PRIVILEGE_IMPERSONATE)
{
IntStatus = ZwSetInformationThread(NtCurrentThread(), ThreadImpersonationToken,
&State->OldImpersonationToken, sizeof(HANDLE));
/* If this ever happens, we're in a really bad situation... */
if (!NT_SUCCESS(IntStatus))
{
RtlRaiseStatus(IntStatus);
}
}
/* Release token */
if (State->Token)
{
ZwClose(State->Token);
}
/* And free our state buffer */
RtlFreeHeap(RtlGetProcessHeap(), 0, State);
DPRINT("RtlAcquirePrivilege() failed with status: %lx\n", Status);
return Status;
}
/*
* @implemented
*/
VOID
NTAPI
RtlReleasePrivilege(IN PVOID ReturnedState)
{
NTSTATUS Status;
PRTL_ACQUIRE_STATE State = (PRTL_ACQUIRE_STATE)ReturnedState;
DPRINT("RtlReleasePrivilege(%p)\n", ReturnedState);
/* If we had an active impersonation before we acquired privileges
* Or if we have impersonated, quit it
*/
if (State->Flags & RTL_ACQUIRE_PRIVILEGE_IMPERSONATE)
{
/* Restore it for the current thread */
Status = ZwSetInformationThread(NtCurrentThread(), ThreadImpersonationToken,
&State->OldImpersonationToken, sizeof(HANDLE));
if (!NT_SUCCESS(Status))
{
RtlRaiseStatus(Status);
}
/* And close the token if needed */
if (State->OldImpersonationToken)
ZwClose(State->OldImpersonationToken);
}
else
{
/* Otherwise, restore old state */
Status = ZwAdjustPrivilegesToken(State->Token, FALSE,
State->OldPrivileges, 0, NULL, NULL);
if (!NT_SUCCESS(Status))
{
RtlRaiseStatus(Status);
}
}
/* If we used a different buffer for old privileges, just free it */
if ((PVOID)State->OldPrivBuffer != (PVOID)State->OldPrivileges)
{
DPRINT("Releasing old privileges: %p\n", State->OldPrivileges);
RtlFreeHeap(RtlGetProcessHeap(), 0, State->OldPrivileges);
}
/* Release token and free state */
ZwClose(State->Token);
RtlFreeHeap(RtlGetProcessHeap(), 0, State);
}
/*
* @implemented
*/
NTSTATUS
NTAPI
RtlAdjustPrivilege(IN ULONG Privilege,
IN BOOLEAN Enable,
IN BOOLEAN CurrentThread,
OUT PBOOLEAN Enabled)
{
TOKEN_PRIVILEGES NewState;
TOKEN_PRIVILEGES OldState;
ULONG ReturnLength;
HANDLE TokenHandle;
NTSTATUS Status;
PAGED_CODE_RTL();
DPRINT("RtlAdjustPrivilege() called\n");
if (CurrentThread)
{
Status = ZwOpenThreadToken(NtCurrentThread(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
FALSE,
&TokenHandle);
}
else
{
Status = ZwOpenProcessToken(NtCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&TokenHandle);
}
if (!NT_SUCCESS (Status))
{
DPRINT1("Retrieving token handle failed (Status %lx)\n", Status);
return Status;
}
OldState.PrivilegeCount = 1;
NewState.PrivilegeCount = 1;
NewState.Privileges[0].Luid.LowPart = Privilege;
NewState.Privileges[0].Luid.HighPart = 0;
NewState.Privileges[0].Attributes = (Enable) ? SE_PRIVILEGE_ENABLED : 0;
Status = ZwAdjustPrivilegesToken(TokenHandle,
FALSE,
&NewState,
sizeof(TOKEN_PRIVILEGES),
&OldState,
&ReturnLength);
ZwClose (TokenHandle);
if (Status == STATUS_NOT_ALL_ASSIGNED)
{
DPRINT1("Failed to assign all privileges\n");
return STATUS_PRIVILEGE_NOT_HELD;
}
if (!NT_SUCCESS(Status))
{
DPRINT1("NtAdjustPrivilegesToken() failed (Status %lx)\n", Status);
return Status;
}
if (OldState.PrivilegeCount == 0)
{
*Enabled = Enable;
}
else
{
*Enabled = (OldState.Privileges[0].Attributes & SE_PRIVILEGE_ENABLED);
}
DPRINT("RtlAdjustPrivilege() done\n");
return STATUS_SUCCESS;
}
#if (NTDDI_VERSION >= NTDDI_VISTA) || (DLL_EXPORT_VERSION >= _WIN32_WINNT_VISTA)
/**
* @brief
* Removes all privileges in the specified access token.
*
* @param[in] TokenHandle
* A handle to the access token that contains the privileges to be removed.
*
* @param[in] PrivilegesToKeep
* A pointer to an array of privilege values (defined as SE_XXX_PRIVILEGE) that specify
* the privileges to keep in the token.
*
* @param[in] PrivilegeCount
* Specifies the number of entries in the PrivilegesToKeep array.
*
* @return
* Returns STATUS_SUCCESS if privileges removed successfully.
* STATUS_INVALID_PARAMETER is returned if input privilege value greater than
* SE_MAX_WELL_KNOWN_PRIVILEGE. STATUS_NOT_ALL_ASSIGNED is returned if The token does
* not have one or more of the privileges specified in the PrivilegesToKeep parameter,
* and no privileges were removed. A failure NTSTATUS code is returned otherwise.
*/
NTSTATUS
NTAPI
RtlRemovePrivileges(
_In_ HANDLE TokenHandle,
_In_reads_opt_(PrivilegeCount) _When_(PrivilegeCount != 0, _Notnull_)
PULONG PrivilegesToKeep,
_In_ ULONG PrivilegeCount)
{
NTSTATUS Status;
UINT64 PrivilegesToKeepBitmap;
ULONG i, ReturnLength;
UCHAR Buffer[sizeof(TOKEN_PRIVILEGES) +
sizeof(LUID_AND_ATTRIBUTES) * (SE_MAX_WELL_KNOWN_PRIVILEGE - SE_MIN_WELL_KNOWN_PRIVILEGE)];
PTOKEN_PRIVILEGES Privileges;
C_ASSERT(SE_MAX_WELL_KNOWN_PRIVILEGE < 64);
DPRINT("RtlRemovePrivileges(%p, %p, %u)\n", TokenHandle, PrivilegesToKeep, PrivilegeCount);
/* Save privileges that should be keep */
PrivilegesToKeepBitmap = 0;
if (PrivilegeCount)
{
for (i = 0; i < PrivilegeCount; i++)
{
if (PrivilegesToKeep[i] > SE_MAX_WELL_KNOWN_PRIVILEGE)
{
return STATUS_INVALID_PARAMETER;
}
PrivilegesToKeepBitmap |= (1ULL << PrivilegesToKeep[i]);
}
}
/* Get token privileges information */
Status = ZwQueryInformationToken(TokenHandle,
TokenPrivileges,
Buffer,
sizeof(Buffer),
&ReturnLength);
if (!NT_SUCCESS(Status))
{
return Status;
}
/* Remove all privileges that we don't need to keep */
Privileges = (PTOKEN_PRIVILEGES)Buffer;
for (i = 0; i < Privileges->PrivilegeCount; i++)
{
LARGE_INTEGER Privilege = *(LARGE_INTEGER*)&Privileges->Privileges[i].Luid;
ASSERT(Privilege.QuadPart <= SE_MAX_WELL_KNOWN_PRIVILEGE);
if (PrivilegesToKeepBitmap & (1ULL << Privilege.QuadPart))
{
PrivilegesToKeepBitmap &= ~(1ULL << Privilege.QuadPart);
}
else
{
Privileges->Privileges[i].Attributes = SE_PRIVILEGE_REMOVED;
}
}
if (PrivilegesToKeepBitmap)
{
Status = STATUS_NOT_ALL_ASSIGNED;
}
else
{
Status = ZwAdjustPrivilegesToken(TokenHandle,
FALSE,
(PTOKEN_PRIVILEGES)Buffer,
sizeof(Buffer),
NULL,
NULL);
}
return Status;
}
#endif /* (NTDDI_VERSION >= NTDDI_VISTA) */