mirror of
https://github.com/reactos/reactos.git
synced 2024-11-20 06:15:26 +00:00
494 lines
16 KiB
C
494 lines
16 KiB
C
|
/*
|
||
|
* PROJECT: ReactOS Win32k subsystem
|
||
|
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
|
||
|
* PURPOSE: Security infrastructure of NTUSER component of Win32k
|
||
|
* COPYRIGHT: Copyright 2022 George Bișoc <george.bisoc@reactos.org>
|
||
|
*/
|
||
|
|
||
|
/* INCLUDES ******************************************************************/
|
||
|
|
||
|
#include <win32k.h>
|
||
|
DBG_DEFAULT_CHANNEL(UserSecurity);
|
||
|
|
||
|
/* FUNCTIONS *****************************************************************/
|
||
|
|
||
|
/**
|
||
|
* @brief
|
||
|
* Opens an access token that represents the effective security
|
||
|
* context of the caller. The purpose of this function is to query
|
||
|
* the authenticated user that is associated with the security
|
||
|
* context.
|
||
|
*
|
||
|
* @return
|
||
|
* Returns a handle to an opened access token that represents the
|
||
|
* security context of the authenticated user, otherwise NULL.
|
||
|
*/
|
||
|
HANDLE
|
||
|
IntGetCurrentAccessToken(VOID)
|
||
|
{
|
||
|
NTSTATUS Status;
|
||
|
HANDLE TokenHandle;
|
||
|
|
||
|
/*
|
||
|
* Try acquiring the security context by opening
|
||
|
* the current thread (or so called impersonation)
|
||
|
* token. Such token represents the effective caller.
|
||
|
* Otherwise if the current thread does not have a
|
||
|
* token (hence no impersonation occurs) then open
|
||
|
* the token of main calling process instead.
|
||
|
*/
|
||
|
Status = ZwOpenThreadToken(ZwCurrentThread(),
|
||
|
TOKEN_QUERY,
|
||
|
FALSE,
|
||
|
&TokenHandle);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
/*
|
||
|
* We might likely fail to open the thread
|
||
|
* token if the process isn't impersonating
|
||
|
* a client. In scenarios where the server
|
||
|
* isn't impersonating, open the main process
|
||
|
* token.
|
||
|
*/
|
||
|
if (Status == STATUS_NO_TOKEN)
|
||
|
{
|
||
|
TRACE("IntGetCurrentAccessToken(): The thread doesn't have a token, trying to open the process one...\n");
|
||
|
Status = ZwOpenProcessToken(ZwCurrentProcess(),
|
||
|
TOKEN_QUERY,
|
||
|
&TokenHandle);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
/* We failed opening process token as well, bail out... */
|
||
|
ERR("IntGetCurrentAccessToken(): Failed to capture security context, couldn't open the process token (Status 0x%08lx)\n", Status);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* Return the opened token handle */
|
||
|
return TokenHandle;
|
||
|
}
|
||
|
|
||
|
/* There's a thread token but we couldn't open it so bail out */
|
||
|
ERR("IntGetCurrentAccessToken(): Failed to capture security context, couldn't open the thread token (Status 0x%08lx)\n", Status);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* Return the opened token handle */
|
||
|
return TokenHandle;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief
|
||
|
* Allocates a buffer within UM (user mode) address
|
||
|
* space area. Such buffer is reserved for security
|
||
|
* purposes, such as allocating a buffer for a DACL
|
||
|
* or a security descriptor.
|
||
|
*
|
||
|
* @param[in] Length
|
||
|
* The length of the buffer that has to be allocated,
|
||
|
* in bytes.
|
||
|
*
|
||
|
* @return
|
||
|
* Returns a pointer to an allocated buffer whose
|
||
|
* contents are arbitrary. If the function fails,
|
||
|
* it means no pages are available to reserve for
|
||
|
* memory allocation for this buffer.
|
||
|
*/
|
||
|
PVOID
|
||
|
IntAllocateSecurityBuffer(
|
||
|
_In_ SIZE_T Length)
|
||
|
{
|
||
|
NTSTATUS Status;
|
||
|
PVOID Buffer = NULL;
|
||
|
|
||
|
/* Allocate the buffer in UM memory space */
|
||
|
Status = ZwAllocateVirtualMemory(ZwCurrentProcess(),
|
||
|
&Buffer,
|
||
|
0,
|
||
|
&Length,
|
||
|
MEM_COMMIT,
|
||
|
PAGE_READWRITE);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
ERR("IntAllocateSecurityBuffer(): Failed to allocate the buffer (Status 0x%08lx)\n", Status);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return Buffer;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief
|
||
|
* Frees an allocated security buffer from UM
|
||
|
* memory that is been previously allocated by
|
||
|
* IntAllocateSecurityBuffer function.
|
||
|
*
|
||
|
* @param[in] Buffer
|
||
|
* A pointer to a buffer whose contents are
|
||
|
* arbitrary, to be freed from UM memory space.
|
||
|
*
|
||
|
* @return
|
||
|
* Nothing.
|
||
|
*/
|
||
|
VOID
|
||
|
IntFreeSecurityBuffer(
|
||
|
_In_ PVOID Buffer)
|
||
|
{
|
||
|
SIZE_T Size = 0;
|
||
|
|
||
|
ZwFreeVirtualMemory(ZwCurrentProcess(),
|
||
|
&Buffer,
|
||
|
&Size,
|
||
|
MEM_RELEASE);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief
|
||
|
* Queries the authenticated user security identifier
|
||
|
* (SID) that is associated with the security context
|
||
|
* of the access token that is being opened.
|
||
|
*
|
||
|
* @param[out] User
|
||
|
* A pointer to the token user that contains the security
|
||
|
* identifier of the authenticated user.
|
||
|
*
|
||
|
* @return
|
||
|
* Returns STATUS_SUCCESS if the function has successfully
|
||
|
* queried the token user. STATUS_UNSUCCESSFUL is returned
|
||
|
* if the effective token of the caller couldn't be opened.
|
||
|
* STATUS_NO_MEMORY is returned if memory allocation for
|
||
|
* token user buffer has failed because of lack of necessary
|
||
|
* pages reserved for such allocation. A failure NTSTATUS
|
||
|
* code is returned otherwise.
|
||
|
*
|
||
|
* @remarks
|
||
|
* !!!WARNING!!! -- THE CALLER WHO QUERIES THE TOKEN USER IS
|
||
|
* RESPONSIBLE TO FREE THE ALLOCATED TOKEN USER BUFFER THAT IS
|
||
|
* BEING GIVEN.
|
||
|
*/
|
||
|
NTSTATUS
|
||
|
IntQueryUserSecurityIdentification(
|
||
|
_Out_ PTOKEN_USER *User)
|
||
|
{
|
||
|
NTSTATUS Status;
|
||
|
PTOKEN_USER UserToken;
|
||
|
HANDLE Token;
|
||
|
ULONG BufferLength;
|
||
|
|
||
|
/* Initialize the parameter */
|
||
|
*User = NULL;
|
||
|
|
||
|
/* Open the current token of the caller */
|
||
|
Token = IntGetCurrentAccessToken();
|
||
|
if (!Token)
|
||
|
{
|
||
|
ERR("IntQueryUserSecurityIdentification(): Couldn't capture the token!\n");
|
||
|
return STATUS_UNSUCCESSFUL;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Since we do not know what the length
|
||
|
* of the buffer size should be exactly to
|
||
|
* hold the user data, let the function
|
||
|
* tell us the size.
|
||
|
*/
|
||
|
Status = ZwQueryInformationToken(Token,
|
||
|
TokenUser,
|
||
|
NULL,
|
||
|
0,
|
||
|
&BufferLength);
|
||
|
if (!NT_SUCCESS(Status) && Status == STATUS_BUFFER_TOO_SMALL)
|
||
|
{
|
||
|
/*
|
||
|
* Allocate some memory for the buffer
|
||
|
* based on the size that the function
|
||
|
* gave us.
|
||
|
*/
|
||
|
UserToken = IntAllocateSecurityBuffer(BufferLength);
|
||
|
if (!UserToken)
|
||
|
{
|
||
|
/* Bail out if we failed */
|
||
|
ERR("IntQueryUserSecurityIdentification(): Couldn't allocate memory for the token user!\n");
|
||
|
ZwClose(Token);
|
||
|
return STATUS_NO_MEMORY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Query the user now as we have plenty of space to hold it */
|
||
|
Status = ZwQueryInformationToken(Token,
|
||
|
TokenUser,
|
||
|
UserToken,
|
||
|
BufferLength,
|
||
|
&BufferLength);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
/* We failed, bail out */
|
||
|
ERR("IntQueryUserSecurityIdentification(): Failed to query token user (Status 0x%08lx)\n", Status);
|
||
|
IntFreeSecurityBuffer(UserToken);
|
||
|
ZwClose(Token);
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
/* All good, give the buffer to the caller and close the captured token */
|
||
|
*User = UserToken;
|
||
|
ZwClose(Token);
|
||
|
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief
|
||
|
* Creates a security descriptor for the service.
|
||
|
*
|
||
|
* @param[out] ServiceSd
|
||
|
* A pointer to a newly allocated and created security
|
||
|
* descriptor for the service.
|
||
|
*
|
||
|
* @return
|
||
|
* Returns STATUS_SUCCESS if the function has successfully
|
||
|
* queried created the security descriptor. STATUS_NO_MEMORY
|
||
|
* is returned if memory allocation for security buffers because
|
||
|
* of a lack of needed pages to reserve for such allocation. A
|
||
|
* failure NTSTATUS code is returned otherwise.
|
||
|
*/
|
||
|
NTSTATUS
|
||
|
NTAPI
|
||
|
IntCreateServiceSecurity(
|
||
|
_Out_ PSECURITY_DESCRIPTOR *ServiceSd)
|
||
|
{
|
||
|
NTSTATUS Status;
|
||
|
PACL ServiceDacl;
|
||
|
ULONG DaclSize;
|
||
|
ULONG RelSDSize;
|
||
|
SECURITY_DESCRIPTOR AbsSD;
|
||
|
PSECURITY_DESCRIPTOR RelSD;
|
||
|
PTOKEN_USER TokenUser;
|
||
|
|
||
|
/* Initialize our local variables */
|
||
|
RelSDSize = 0;
|
||
|
TokenUser = NULL;
|
||
|
RelSD = NULL;
|
||
|
ServiceDacl = NULL;
|
||
|
|
||
|
/* Query the logged in user of the current security context (aka token) */
|
||
|
Status = IntQueryUserSecurityIdentification(&TokenUser);
|
||
|
if (!TokenUser)
|
||
|
{
|
||
|
ERR("IntCreateServiceSecurity(): Failed to query the token user (Status 0x%08lx)\n", Status);
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
/* Initialize the absolute security descriptor */
|
||
|
Status = RtlCreateSecurityDescriptor(&AbsSD, SECURITY_DESCRIPTOR_REVISION);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
ERR("IntCreateServiceSecurity(): Failed to initialize absolute SD (Status 0x%08lx)\n", Status);
|
||
|
goto Quit;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Build up the size of access control
|
||
|
* list (the DACL) necessary to initialize
|
||
|
* our ACL. The first two entry members
|
||
|
* of ACL field are the authenticated user
|
||
|
* that is associated with the security
|
||
|
* context of the token. Then here come
|
||
|
* the last two entries which are admins.
|
||
|
* Why the ACL contains two ACEs of the
|
||
|
* same SID is because of service access
|
||
|
* rights and ACE inheritance.
|
||
|
*
|
||
|
* A service is composed of a default
|
||
|
* desktop and window station upon
|
||
|
* booting the system. On Windows connection
|
||
|
* to such service is being made if no
|
||
|
* default window station and desktop handles
|
||
|
* were created before. The desktop and winsta
|
||
|
* objects grant access on a separate type basis.
|
||
|
* The user is granted full access to the window
|
||
|
* station first and then full access to the desktop.
|
||
|
* After that admins are granted specific rights
|
||
|
* separately, just like the user. Ultimately the
|
||
|
* ACEs that handle desktop rights management are
|
||
|
* inherited to the default desktop object so
|
||
|
* that there's no need to have a separate security
|
||
|
* descriptor for the desktop object alone.
|
||
|
*/
|
||
|
DaclSize = sizeof(ACL) +
|
||
|
sizeof(ACCESS_ALLOWED_ACE) + RtlLengthSid(TokenUser->User.Sid) +
|
||
|
sizeof(ACCESS_ALLOWED_ACE) + RtlLengthSid(TokenUser->User.Sid) +
|
||
|
sizeof(ACCESS_ALLOWED_ACE) + RtlLengthSid(SeExports->SeAliasAdminsSid) +
|
||
|
sizeof(ACCESS_ALLOWED_ACE) + RtlLengthSid(SeExports->SeAliasAdminsSid);
|
||
|
|
||
|
/* Allocate memory for service DACL */
|
||
|
ServiceDacl = IntAllocateSecurityBuffer(DaclSize);
|
||
|
if (!ServiceDacl)
|
||
|
{
|
||
|
ERR("IntCreateServiceSecurity(): Failed to allocate memory for service DACL!\n");
|
||
|
Status = STATUS_NO_MEMORY;
|
||
|
goto Quit;
|
||
|
}
|
||
|
|
||
|
/* Now create the DACL */
|
||
|
Status = RtlCreateAcl(ServiceDacl,
|
||
|
DaclSize,
|
||
|
ACL_REVISION);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
ERR("IntCreateServiceSecurity(): Failed to create service DACL (Status 0x%08lx)\n", Status);
|
||
|
goto Quit;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* The authenticated user is the ultimate and absolute
|
||
|
* king in charge of the created (or opened, whatever that is)
|
||
|
* window station object.
|
||
|
*/
|
||
|
Status = RtlAddAccessAllowedAceEx(ServiceDacl,
|
||
|
ACL_REVISION,
|
||
|
0,
|
||
|
WINSTA_ACCESS_ALL,
|
||
|
TokenUser->User.Sid);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
ERR("IntCreateServiceSecurity(): Failed to set up window station ACE for authenticated user (Status 0x%08lx)\n", Status);
|
||
|
goto Quit;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* The authenticated user also has the ultimate power
|
||
|
* over the desktop object as well. This ACE cannot
|
||
|
* be propagated but inherited. See the comment
|
||
|
* above regarding ACL size for further explanation.
|
||
|
*/
|
||
|
Status = RtlAddAccessAllowedAceEx(ServiceDacl,
|
||
|
ACL_REVISION,
|
||
|
INHERIT_ONLY_ACE | NO_PROPAGATE_INHERIT_ACE | OBJECT_INHERIT_ACE,
|
||
|
DESKTOP_ALL_ACCESS,
|
||
|
TokenUser->User.Sid);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
ERR("IntCreateServiceSecurity(): Failed to set up desktop ACE for authenticated user (Status 0x%08lx)\n", Status);
|
||
|
goto Quit;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Administrators can only enumerate window
|
||
|
* stations within a desktop.
|
||
|
*/
|
||
|
Status = RtlAddAccessAllowedAceEx(ServiceDacl,
|
||
|
ACL_REVISION,
|
||
|
0,
|
||
|
WINSTA_ENUMERATE,
|
||
|
SeExports->SeAliasAdminsSid);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
ERR("IntCreateServiceSecurity(): Failed to set up window station ACE for admins (Status 0x%08lx)\n", Status);
|
||
|
goto Quit;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Administrators have some share of power over
|
||
|
* the desktop object. They can enumerate desktops,
|
||
|
* write and read upon the object itself.
|
||
|
*/
|
||
|
Status = RtlAddAccessAllowedAceEx(ServiceDacl,
|
||
|
ACL_REVISION,
|
||
|
INHERIT_ONLY_ACE | NO_PROPAGATE_INHERIT_ACE | OBJECT_INHERIT_ACE,
|
||
|
DESKTOP_ENUMERATE | DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS,
|
||
|
SeExports->SeAliasAdminsSid);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
ERR("IntCreateServiceSecurity(): Failed to set up desktop ACE for admins (Status 0x%08lx)\n", Status);
|
||
|
goto Quit;
|
||
|
}
|
||
|
|
||
|
/* Set the DACL to absolute SD */
|
||
|
Status = RtlSetDaclSecurityDescriptor(&AbsSD,
|
||
|
TRUE,
|
||
|
ServiceDacl,
|
||
|
FALSE);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
ERR("IntCreateServiceSecurity(): Failed to set up service DACL to absolute SD (Status 0x%08lx)\n", Status);
|
||
|
goto Quit;
|
||
|
}
|
||
|
|
||
|
/* This descriptor is ownerless */
|
||
|
Status = RtlSetOwnerSecurityDescriptor(&AbsSD,
|
||
|
NULL,
|
||
|
FALSE);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
ERR("IntCreateServiceSecurity(): Failed to make the absolute SD as ownerless (Status 0x%08lx)\n", Status);
|
||
|
goto Quit;
|
||
|
}
|
||
|
|
||
|
/* This descriptor has no primary group */
|
||
|
Status = RtlSetGroupSecurityDescriptor(&AbsSD,
|
||
|
NULL,
|
||
|
FALSE);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
ERR("IntCreateServiceSecurity(): Failed to make the absolute SD as having no primary group (Status 0x%08lx)\n", Status);
|
||
|
goto Quit;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Determine how much size is needed to allocate
|
||
|
* memory space for our relative security descriptor.
|
||
|
*/
|
||
|
Status = RtlAbsoluteToSelfRelativeSD(&AbsSD,
|
||
|
NULL,
|
||
|
&RelSDSize);
|
||
|
if (Status != STATUS_BUFFER_TOO_SMALL)
|
||
|
{
|
||
|
ERR("IntCreateServiceSecurity(): Unexpected status code, must be STATUS_BUFFER_TOO_SMALL (Status 0x%08lx)\n", Status);
|
||
|
goto Quit;
|
||
|
}
|
||
|
|
||
|
/* Allocate memory for this */
|
||
|
RelSD = IntAllocateSecurityBuffer(RelSDSize);
|
||
|
if (!RelSD)
|
||
|
{
|
||
|
ERR("IntCreateServiceSecurity(): Failed to allocate memory pool for relative SD!\n");
|
||
|
Status = STATUS_NO_MEMORY;
|
||
|
goto Quit;
|
||
|
}
|
||
|
|
||
|
/* Convert the absolute SD into a relative one now */
|
||
|
Status = RtlAbsoluteToSelfRelativeSD(&AbsSD,
|
||
|
RelSD,
|
||
|
&RelSDSize);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
ERR("IntCreateServiceSecurity(): Failed to convert absolute SD to a relative one (Status 0x%08lx)\n", Status);
|
||
|
goto Quit;
|
||
|
}
|
||
|
|
||
|
/* All good, give the SD to the caller */
|
||
|
*ServiceSd = RelSD;
|
||
|
|
||
|
Quit:
|
||
|
if (ServiceDacl)
|
||
|
{
|
||
|
IntFreeSecurityBuffer(ServiceDacl);
|
||
|
}
|
||
|
|
||
|
if (TokenUser)
|
||
|
{
|
||
|
IntFreeSecurityBuffer(TokenUser);
|
||
|
}
|
||
|
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
if (RelSD)
|
||
|
{
|
||
|
IntFreeSecurityBuffer(RelSD);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
/* EOF */
|