https://git.reactos.org/?p=reactos.git;a=commitdiff;h=d862fa6fc89b8864ce16a…
commit d862fa6fc89b8864ce16a5a44c96fabb15621d09
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Sat Mar 26 22:36:03 2022 +0100
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Fri May 6 10:09:50 2022 +0200
[ADVAPI32] Implement security descriptor management in CreateProcessAsUserCommon internal function
Currently CreateProcessAsUserCommon doesn't set a default descriptor for the newly duplicated token object for the new process nor it sets any security information for both the process and thread. This is wrong, because when the process is created on behalf of the user's security context,
it still uses the previous security information of the creator that initially gave birth to the process. CreateDefaultProcessSecurityCommon function will serve as a placeholder until CreatePrivateObjectSecurity is implemented.
---
dll/win32/advapi32/misc/logon.c | 433 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 431 insertions(+), 2 deletions(-)
diff --git a/dll/win32/advapi32/misc/logon.c b/dll/win32/advapi32/misc/logon.c
index 6687a9f7e93..66abfc199f7 100644
--- a/dll/win32/advapi32/misc/logon.c
+++ b/dll/win32/advapi32/misc/logon.c
@@ -89,6 +89,346 @@ CloseLogonLsaHandle(VOID)
}
+/**
+ * @brief
+ * Creates a default security descriptor that is going
+ * to be used by both the newly created process and thread
+ * by a call to CreateProcessAsUserA/W. This descriptor also
+ * serves for the newly duplicated token object that is going
+ * to be set for the token which acts as the main user.
+ *
+ * @param[in] TokenHandle
+ * A handle to a token. The function will use this token to
+ * query security details such as the owner and primary group
+ * associated with the security context of this token. The
+ * obtained information will then be assigned to the security
+ * descriptor.
+ *
+ * @param[out] Sd
+ * A pointer to an allocated security descriptor that is given
+ * to the caller.
+ *
+ * @return
+ * Return TRUE if the security descriptor has been successfully
+ * created, FALSE otherwise.
+ *
+ * @remarks
+ * When a process is created on behald of the user's security context
+ * this user will be the owner and responsible for that process. Whatever
+ * objects created or stuff done within the process space is at the
+ * discretion of the user, that is, further objects created are in
+ * charge by the user himself as is the owner of the process.
+ *
+ * !!!NOTE!!! -- On Windows the security descriptor is created by using
+ * CreatePrivateObjectSecurity(Ex) API call. Whilst the way the security
+ * descriptor is created in our end is not wrong per se, this function
+ * serves a placeholder until CreatePrivateObjectSecurity is implemented.
+ */
+static
+BOOL
+CreateDefaultProcessSecurityCommon(
+ _In_ HANDLE TokenHandle,
+ _Out_ PSECURITY_DESCRIPTOR *Sd)
+{
+ NTSTATUS Status;
+ BOOL Success;
+ PACL Dacl;
+ PTOKEN_OWNER OwnerOfToken;
+ PTOKEN_PRIMARY_GROUP PrimaryGroupOfToken;
+ SECURITY_DESCRIPTOR AbsoluteSd;
+ ULONG DaclSize, TokenOwnerSize, PrimaryGroupSize, RelativeSDSize = 0;
+ PSID OwnerSid = NULL, SystemSid = NULL, PrimaryGroupSid = NULL;
+ PSECURITY_DESCRIPTOR RelativeSD = NULL;
+ static SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
+
+ /*
+ * Since we do not know how much space
+ * is needed to allocate the buffer to
+ * hold the token owner, first we must
+ * query the exact size.
+ */
+ Status = NtQueryInformationToken(TokenHandle,
+ TokenOwner,
+ NULL,
+ 0,
+ &TokenOwnerSize);
+ if (Status != STATUS_BUFFER_TOO_SMALL)
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Unexpected status code returned, must be STATUS_BUFFER_TOO_SMALL (Status 0x%08lx)\n", Status);
+ return FALSE;
+ }
+
+ /* We have the required space size, allocate the buffer now */
+ OwnerOfToken = RtlAllocateHeap(RtlGetProcessHeap(),
+ HEAP_ZERO_MEMORY,
+ TokenOwnerSize);
+ if (OwnerOfToken == NULL)
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to allocate buffer for token owner!\n");
+ return FALSE;
+ }
+
+ /* Now query the token owner */
+ Status = NtQueryInformationToken(TokenHandle,
+ TokenOwner,
+ OwnerOfToken,
+ TokenOwnerSize,
+ &TokenOwnerSize);
+ if (!NT_SUCCESS(Status))
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to query the token owner (Status 0x%08lx)\n", Status);
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Do the same process but for the primary group now */
+ Status = NtQueryInformationToken(TokenHandle,
+ TokenPrimaryGroup,
+ NULL,
+ 0,
+ &PrimaryGroupSize);
+ if (Status != STATUS_BUFFER_TOO_SMALL)
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Unexpected status code returned, must be STATUS_BUFFER_TOO_SMALL (Status 0x%08lx)\n", Status);
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Allocate the buffer */
+ PrimaryGroupOfToken = RtlAllocateHeap(RtlGetProcessHeap(),
+ HEAP_ZERO_MEMORY,
+ PrimaryGroupSize);
+ if (PrimaryGroupOfToken == NULL)
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to allocate buffer for primary group token!\n");
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Query the primary group now */
+ Status = NtQueryInformationToken(TokenHandle,
+ TokenPrimaryGroup,
+ PrimaryGroupOfToken,
+ PrimaryGroupSize,
+ &PrimaryGroupSize);
+ if (!NT_SUCCESS(Status))
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to query the token owner (Status 0x%08lx)\n", Status);
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Create the SYSTEM SID */
+ if (!AllocateAndInitializeSid(&NtAuthority,
+ 1,
+ SECURITY_LOCAL_SYSTEM_RID,
+ 0, 0, 0, 0, 0, 0, 0,
+ &SystemSid))
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to create Local System SID (error code %d)\n", GetLastError());
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Cache the token owner and primary group SID */
+ OwnerSid = OwnerOfToken->Owner;
+ PrimaryGroupSid = PrimaryGroupOfToken->PrimaryGroup;
+
+ /* Set up the DACL size */
+ DaclSize = sizeof(ACL) +
+ sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(OwnerSid) +
+ sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(SystemSid);
+
+ /* Allocate buffer for the DACL */
+ Dacl = RtlAllocateHeap(RtlGetProcessHeap(),
+ HEAP_ZERO_MEMORY,
+ DaclSize);
+ if (Dacl == NULL)
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to allocate buffer for DACL!\n");
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Initialize the DACL */
+ if (!InitializeAcl(Dacl, DaclSize, ACL_REVISION))
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to initialize DACL (error code %d)\n", GetLastError());
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Give full powers to the owner */
+ if (!AddAccessAllowedAce(Dacl,
+ ACL_REVISION,
+ GENERIC_ALL,
+ OwnerSid))
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to set up ACE for owner (error code %d)\n", GetLastError());
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Give full powers to SYSTEM as well */
+ if (!AddAccessAllowedAce(Dacl,
+ ACL_REVISION,
+ GENERIC_ALL,
+ SystemSid))
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to set up ACE for SYSTEM (error code %d)\n", GetLastError());
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Initialize the descriptor in absolute format */
+ if (!InitializeSecurityDescriptor(&AbsoluteSd, SECURITY_DESCRIPTOR_REVISION))
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to initialize absolute security descriptor (error code %d)\n", GetLastError());
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Set the DACL to the security descriptor */
+ if (!SetSecurityDescriptorDacl(&AbsoluteSd, TRUE, Dacl, FALSE))
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to set up DACL to absolute security descriptor (error code %d)\n", GetLastError());
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Set the owner for this descriptor */
+ if (!SetSecurityDescriptorOwner(&AbsoluteSd, OwnerSid, FALSE))
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to set up owner to absolute security descriptor (error code %d)\n", GetLastError());
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Set the primary group for this descriptor */
+ if (!SetSecurityDescriptorGroup(&AbsoluteSd, PrimaryGroupSid, FALSE))
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to set up group to absolute security descriptor (error code %d)\n", GetLastError());
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /*
+ * Determine the exact size space of the absolute
+ * descriptor so that we can allocate a buffer
+ * to hold the descriptor in a converted self
+ * relative format.
+ */
+ if (!MakeSelfRelativeSD(&AbsoluteSd, NULL, &RelativeSDSize) && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Unexpected error code (error code %d -- must be ERROR_INSUFFICIENT_BUFFER)\n", GetLastError());
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Allocate the buffer */
+ RelativeSD = RtlAllocateHeap(RtlGetProcessHeap(),
+ HEAP_ZERO_MEMORY,
+ RelativeSDSize);
+ if (RelativeSD == NULL)
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to allocate buffer for self relative descriptor!\n");
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Convert to a self relative format now */
+ if (!MakeSelfRelativeSD(&AbsoluteSd, RelativeSD, &RelativeSDSize))
+ {
+ ERR("CreateDefaultProcessSecurityCommon(): Failed to allocate relative SD, buffer too smal (error code %d)\n", GetLastError());
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /* Success, give the descriptor to the caller */
+ *Sd = RelativeSD;
+ Success = TRUE;
+
+Quit:
+ /* Free all the stuff we have allocated */
+ if (OwnerOfToken != NULL)
+ RtlFreeHeap(RtlGetProcessHeap(), 0, OwnerOfToken);
+
+ if (PrimaryGroupOfToken != NULL)
+ RtlFreeHeap(RtlGetProcessHeap(), 0, PrimaryGroupOfToken);
+
+ if (SystemSid != NULL)
+ FreeSid(SystemSid);
+
+ if (Dacl != NULL)
+ RtlFreeHeap(RtlGetProcessHeap(), 0, Dacl);
+
+ if (Success == FALSE)
+ {
+ if (RelativeSD != NULL)
+ {
+ RtlFreeHeap(RtlGetProcessHeap(), 0, RelativeSD);
+ }
+ }
+
+ return Success;
+}
+
+
+/**
+ * @brief
+ * Changes the object security information of a process
+ * and thread that belongs to the process with new security
+ * data, basically by replacing the previous security descriptor
+ * with a new one.
+ *
+ * @param[in] ProcessHandle
+ * A handle to a valid process of which security information is
+ * to be changed by setting up a new security descriptor.
+ *
+ * @param[in] ThreadHandle
+ * A handle to a valid thread of which security information is
+ * to be changed by setting up a new security descriptor.
+ *
+ * @param[in] ProcessSecurity
+ * A pointer to a security descriptor that is for the process.
+ *
+ * @param[in] ThreadSecurity
+ * A pointer to a security descriptor that is for the thread.
+ *
+ * @return
+ * Return TRUE if new security information has been set, FALSE
+ * otherwise.
+ */
+static
+BOOL
+InsertProcessSecurityCommon(
+ _In_ HANDLE ProcessHandle,
+ _In_ HANDLE ThreadHandle,
+ _In_ PSECURITY_DESCRIPTOR ProcessSecurity,
+ _In_ PSECURITY_DESCRIPTOR ThreadSecurity)
+{
+ /* Set new security data for the process */
+ if (!SetKernelObjectSecurity(ProcessHandle,
+ DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION,
+ ProcessSecurity))
+ {
+ ERR("InsertProcessSecurityCommon(): Failed to set security for process (error code %d)\n", GetLastError());
+ return FALSE;
+ }
+
+ /* Set new security data for the thread */
+ if (!SetKernelObjectSecurity(ThreadHandle,
+ DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION,
+ ThreadSecurity))
+ {
+ ERR("InsertProcessSecurityCommon(): Failed to set security for thread (error code %d)\n", GetLastError());
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
/**
* @brief
* Sets a primary token to the newly created process.
@@ -267,6 +607,20 @@ InsertTokenToProcessCommon(
* if the process wasn't created in a suspended way
* and if not the function will resume the main thread.
*
+ * @param[in] lpProcessAttributes
+ * A pointer to process attributes. This function uses
+ * this parameter to gather the security descriptor,
+ * if ever present. If it is, this descriptor takes
+ * precedence over the default one when setting
+ * new security information to the process.
+ *
+ * @param[in] lpThreadAttributes
+ * A pointer to thread attributes. This function uses
+ * this parameter to gather the security descriptor,
+ * if ever present. If it is, this descriptor takes
+ * precedence over the default one when setting
+ * new security information to the thread.
+ *
* @param[in,out] lpProcessInformation
* A pointer to a structure that contains process creation
* information data. Such pointer contains the process
@@ -291,6 +645,8 @@ BOOL
CreateProcessAsUserCommon(
_In_opt_ HANDLE hToken,
_In_ DWORD dwCreationFlags,
+ _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_Inout_ LPPROCESS_INFORMATION lpProcessInformation)
{
NTSTATUS Status = STATUS_SUCCESS, StatusOnExit;
@@ -298,6 +654,7 @@ CreateProcessAsUserCommon(
TOKEN_TYPE Type;
ULONG ReturnLength;
OBJECT_ATTRIBUTES ObjectAttributes;
+ PSECURITY_DESCRIPTOR DefaultSd = NULL, ProcessSd, ThreadSd;
HANDLE hTokenDup = NULL;
HANDLE OriginalImpersonationToken = NULL;
HANDLE NullToken = NULL;
@@ -367,12 +724,27 @@ CreateProcessAsUserCommon(
}
}
- /* Duplicate the token for this new process */
+ /*
+ * Create a security descriptor that will be common for the
+ * newly created process on behalf of the context user.
+ */
+ if (!CreateDefaultProcessSecurityCommon(hToken, &DefaultSd))
+ {
+ ERR("Failed to create common security descriptor for the token for new process!\n");
+ Success = FALSE;
+ goto Quit;
+ }
+
+ /*
+ * Duplicate the token for this new process. This token
+ * object will get a default security descriptor that we
+ * have created ourselves in ADVAPI32.
+ */
InitializeObjectAttributes(&ObjectAttributes,
NULL,
0,
NULL,
- NULL); // FIXME: Use a valid SecurityDescriptor!
+ DefaultSd);
Status = NtDuplicateToken(hToken,
0,
&ObjectAttributes,
@@ -452,6 +824,53 @@ CreateProcessAsUserCommon(
* this user.
*/
+ /*
+ * As we have successfully set the token into the process now
+ * it is time that we set up new security information for both
+ * the process and its thread as well, that is, these securable
+ * objects will grant a security descriptor. The security descriptors
+ * provided by the caller take precedence so we should use theirs
+ * if possible in this case. Otherwise both the process and thread
+ * will receive the default security descriptor that we have created
+ * ourselves.
+ *
+ * BEAR IN MIND!!! AT THE MOMENT when these securable objects get new
+ * security information, the process (and the thread) can't be opened
+ * by the creator anymore as the new owner will take in charge of
+ * the process and future objects that are going to be created within
+ * the process. For further information in regard of the documentation
+ * see https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-pro….
+ */
+ if (lpProcessAttributes && lpProcessAttributes->lpSecurityDescriptor)
+ {
+ ProcessSd = lpProcessAttributes->lpSecurityDescriptor;
+ }
+ else
+ {
+ ProcessSd = DefaultSd;
+ }
+
+ if (lpThreadAttributes && lpThreadAttributes->lpSecurityDescriptor)
+ {
+ ThreadSd = lpThreadAttributes->lpSecurityDescriptor;
+ }
+ else
+ {
+ ThreadSd = DefaultSd;
+ }
+
+ /* Set new security info to the process and thread now */
+ if (!InsertProcessSecurityCommon(lpProcessInformation->hProcess,
+ lpProcessInformation->hThread,
+ ProcessSd,
+ ThreadSd))
+ {
+ ERR("Failed to set new security information for process and thread!\n");
+ NtClose(hTokenDup);
+ Success = FALSE;
+ goto Quit;
+ }
+
/* Close the duplicated token */
NtClose(hTokenDup);
Success = TRUE;
@@ -507,6 +926,12 @@ Quit:
ResumeThread(lpProcessInformation->hThread);
}
+ /* Free the security descriptor from memory */
+ if (DefaultSd != NULL)
+ {
+ RtlFreeHeap(RtlGetProcessHeap(), 0, DefaultSd);
+ }
+
return Success;
}
@@ -553,6 +978,8 @@ CreateProcessAsUserA(
/* Call the helper function */
return CreateProcessAsUserCommon(hToken,
dwCreationFlags,
+ lpProcessAttributes,
+ lpThreadAttributes,
lpProcessInformation);
}
@@ -599,6 +1026,8 @@ CreateProcessAsUserW(
/* Call the helper function */
return CreateProcessAsUserCommon(hToken,
dwCreationFlags,
+ lpProcessAttributes,
+ lpThreadAttributes,
lpProcessInformation);
}
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=e90e918039e60228d1c3e…
commit e90e918039e60228d1c3e3a7e8bc016915dd49ac
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Thu Mar 10 12:03:31 2022 +0100
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Fri May 6 10:09:50 2022 +0200
[ADVAPI32] Soft rewrite of CreateProcessAsUserCommon
Refactor the function in such a way that it can jump to a single exit but most importantly, implement a "rinse and repeat" mechanism where we assign a primary token to process by disabling impersonation first and retry with impersonation later.
More info can be found in the documention within the code.
---
dll/win32/advapi32/misc/logon.c | 419 +++++++++++++++++++++++++++++++++-------
1 file changed, 352 insertions(+), 67 deletions(-)
diff --git a/dll/win32/advapi32/misc/logon.c b/dll/win32/advapi32/misc/logon.c
index 1262f1b652d..6687a9f7e93 100644
--- a/dll/win32/advapi32/misc/logon.c
+++ b/dll/win32/advapi32/misc/logon.c
@@ -89,24 +89,221 @@ CloseLogonLsaHandle(VOID)
}
+/**
+ * @brief
+ * Sets a primary token to the newly created process.
+ * The primary token that gets assigned to is a token
+ * whose security context is associated with the logged
+ * in user. For futher documentation information, see
+ * Remarks.
+ *
+ * @param[in] ImpersonateAsSelf
+ * If set to TRUE, the function will act on behalf of
+ * the calling process by impersonating its security context.
+ * Generally the caller will disable impersonation and attempt
+ * to act on behalf of the said main process as a first tentative
+ * to acquire the needed privilege in order to assign a token
+ * to the process. If set to FALSE, the function won't act on behalf
+ * of the calling process.
+ *
+ * @param[in] ProcessHandle
+ * A handle to the newly created process. The function will use it
+ * as a mean to assign the primary token to this process.
+ *
+ * @param[in] ThreadHandle
+ * A handle to the newly and primary created thread associated with
+ * the process.
+ *
+ * @param[in] DuplicatedTokenHandle
+ * A handle to a duplicated access token. This token represents as a primary
+ * one, initially duplicated in form as a primary type from an impersonation
+ * type.
+ *
+ * @return
+ * STATUS_SUCCESS is returned if token assignment to process succeeded, otherwise
+ * a failure NTSTATUS code is returned. A potential failure status code is
+ * STATUS_ACCESS_DENIED which means the caller doesn't have enough rights
+ * to grant access for primary token assignment to process.
+ *
+ * @remarks
+ * This function acts like an internal helper for CreateProcessAsUserCommon (and as
+ * such for CreateProcessAsUserW/A as well) as once a process is created, the
+ * function is tasked to assign the security context of the logged in user to
+ * that process. However, the rate of success of inserting the token into the
+ * process ultimately depends on the caller.
+ *
+ * The caller will either succeed or fail at acquiring SE_ASSIGNPRIMARYTOKEN_PRIVILEGE
+ * privilege depending on the security context of the user. If it's allowed, the caller
+ * would generally acquire such privilege immediately but if not, the caller will attempt
+ * to do a second try.
+ */
+static
+NTSTATUS
+InsertTokenToProcessCommon(
+ _In_ BOOL ImpersonateAsSelf,
+ _In_ HANDLE ProcessHandle,
+ _In_ HANDLE ThreadHandle,
+ _In_ HANDLE DuplicatedTokenHandle)
+{
+ NTSTATUS Status;
+ PROCESS_ACCESS_TOKEN AccessToken;
+ BOOLEAN PrivilegeSet;
+ BOOLEAN HavePrivilege;
+
+ /*
+ * Assume the SE_ASSIGNPRIMARYTOKEN_PRIVILEGE
+ * privilege hasn't been set.
+ */
+ PrivilegeSet = FALSE;
+
+ /*
+ * The caller asked that we must impersonate as
+ * ourselves, that is, we'll be going to impersonate
+ * the security context of the calling process. If
+ * self impersonation fails then the caller has
+ * to do a "rinse and repeat" approach.
+ */
+ if (ImpersonateAsSelf)
+ {
+ Status = RtlImpersonateSelf(SecurityImpersonation);
+ if (!NT_SUCCESS(Status))
+ {
+ ERR("RtlImpersonateSelf(SecurityImpersonation) failed, Status 0x%08x\n", Status);
+ return Status;
+ }
+ }
+
+ /*
+ * Attempt to acquire the process primary token assignment privilege
+ * in case we actually need it.
+ * The call will either succeed or fail when the caller has (or has not)
+ * enough rights.
+ * The last situation may not be dramatic for us. Indeed it may happen
+ * that the user-provided token is a restricted version of the caller's
+ * primary token (aka. a "child" token), or both tokens inherit (i.e. are
+ * children, and are together "siblings") from a common parent token.
+ * In this case the NT kernel allows us to assign the token to the child
+ * process without the need for the assignment privilege, which is fine.
+ * On the contrary, if the user-provided token is completely arbitrary,
+ * then the NT kernel will enforce the presence of the assignment privilege:
+ * because we failed (by assumption) to assign the privilege, the process
+ * token assignment will fail as required. It is then the job of the
+ * caller to manually acquire the necessary privileges.
+ */
+ Status = RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE,
+ TRUE, TRUE, &PrivilegeSet);
+ HavePrivilege = NT_SUCCESS(Status);
+ if (!HavePrivilege)
+ {
+ ERR("RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE) failed, Status 0x%08lx, "
+ "attempting to continue without it...\n", Status);
+ }
+
+ /*
+ * Assign the duplicated token and thread
+ * handle to the structure so that we'll
+ * use it to assign the primary token
+ * to process.
+ */
+ AccessToken.Token = DuplicatedTokenHandle;
+ AccessToken.Thread = ThreadHandle;
+
+ /* Set the new process token */
+ Status = NtSetInformationProcess(ProcessHandle,
+ ProcessAccessToken,
+ (PVOID)&AccessToken,
+ sizeof(AccessToken));
+
+ /* Restore the privilege */
+ if (HavePrivilege)
+ {
+ RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE,
+ PrivilegeSet, TRUE, &PrivilegeSet);
+ }
+
+ /*
+ * Check again if the caller wanted to impersonate
+ * as self. If that is the case we must revert this
+ * impersonation back.
+ */
+ if (ImpersonateAsSelf)
+ {
+ RevertToSelf();
+ }
+
+ /*
+ * Finally, check if we actually succeeded on assigning
+ * a primary token to the process. If we failed, oh well,
+ * asta la vista baby e arrivederci. The caller has to do
+ * a rinse and repeat approach.
+ */
+ if (!NT_SUCCESS(Status))
+ {
+ ERR("Failed to assign primary token to the process (Status 0x%08lx)\n", Status);
+ return Status;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+/**
+ * @brief
+ * Internal function that serves as a helper for
+ * CreateProcessAsUserW/A routines on creating
+ * a process within the context of the logged in
+ * user.
+ *
+ * @param[in] hToken
+ * A handle to an access token that is associated
+ * with the logged in user. If the caller does not
+ * submit a token, the helper will immediately quit
+ * and return success, and the newly created process
+ * will be created upon using the default security
+ * context.
+ *
+ * @param[in] dwCreationFlags
+ * Bit masks containing the creation process flags.
+ * The function uses this parameter to determine
+ * if the process wasn't created in a suspended way
+ * and if not the function will resume the main thread.
+ *
+ * @param[in,out] lpProcessInformation
+ * A pointer to a structure that contains process creation
+ * information data. Such pointer contains the process
+ * and thread handles and whatnot.
+ *
+ * @return
+ * Returns TRUE if the helper has successfully assigned
+ * the newly created process the user's security context
+ * to that process, otherwise FALSE is returned.
+ *
+ * @remarks
+ * In order for the helper function to assign the primary
+ * token to the process, it has to do a "rinse and repeat"
+ * approach. That is, the helper will stop the impersonation
+ * and attempt to assign the token to process by acting
+ * on behalf of the main process' security context. If that
+ * fails, the function will do a second attempt by doing this
+ * but with impersonation enabled instead.
+ */
static
BOOL
CreateProcessAsUserCommon(
_In_opt_ HANDLE hToken,
_In_ DWORD dwCreationFlags,
- _Out_ LPPROCESS_INFORMATION lpProcessInformation)
+ _Inout_ LPPROCESS_INFORMATION lpProcessInformation)
{
- NTSTATUS Status;
- PROCESS_ACCESS_TOKEN AccessToken;
+ NTSTATUS Status = STATUS_SUCCESS, StatusOnExit;
+ BOOL Success;
+ TOKEN_TYPE Type;
+ ULONG ReturnLength;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ HANDLE hTokenDup = NULL;
+ HANDLE OriginalImpersonationToken = NULL;
+ HANDLE NullToken = NULL;
if (hToken != NULL)
{
- TOKEN_TYPE Type;
- ULONG ReturnLength;
- OBJECT_ATTRIBUTES ObjectAttributes;
- HANDLE hTokenDup;
- BOOLEAN PrivilegeSet = FALSE, HavePrivilege;
-
/* Check whether the user-provided token is a primary token */
// GetTokenInformation();
Status = NtQueryInformationToken(hToken,
@@ -117,15 +314,59 @@ CreateProcessAsUserCommon(
if (!NT_SUCCESS(Status))
{
ERR("NtQueryInformationToken() failed, Status 0x%08x\n", Status);
+ Success = FALSE;
goto Quit;
}
+
if (Type != TokenPrimary)
{
ERR("Wrong token type for token 0x%p, expected TokenPrimary, got %ld\n", hToken, Type);
Status = STATUS_BAD_TOKEN_TYPE;
+ Success = FALSE;
goto Quit;
}
+ /*
+ * Open the original token of the calling thread
+ * and halt the impersonation for the moment
+ * being. The opened thread token will be cached
+ * so that we will restore it back when we're done.
+ */
+ Status = NtOpenThreadToken(NtCurrentThread(),
+ TOKEN_QUERY | TOKEN_IMPERSONATE,
+ TRUE,
+ &OriginalImpersonationToken);
+ if (!NT_SUCCESS(Status))
+ {
+ /* We failed? Does this thread have a token at least? */
+ OriginalImpersonationToken = NULL;
+ if (Status != STATUS_NO_TOKEN)
+ {
+ /*
+ * OK so this thread has a token but we
+ * could not open it for whatever reason.
+ * Bail out then.
+ */
+ ERR("Failed to open thread token with 0x%08lx\n", Status);
+ Success = FALSE;
+ goto Quit;
+ }
+ }
+ else
+ {
+ /* We succeeded, stop the impersonation for now */
+ Status = NtSetInformationThread(NtCurrentThread(),
+ ThreadImpersonationToken,
+ &NullToken,
+ sizeof(NullToken));
+ if (!NT_SUCCESS(Status))
+ {
+ ERR("Failed to stop impersonation with 0x%08lx\n", Status);
+ Success = FALSE;
+ goto Quit;
+ }
+ }
+
/* Duplicate the token for this new process */
InitializeObjectAttributes(&ObjectAttributes,
NULL,
@@ -141,79 +382,123 @@ CreateProcessAsUserCommon(
if (!NT_SUCCESS(Status))
{
ERR("NtDuplicateToken() failed, Status 0x%08x\n", Status);
- goto Quit;
- }
-
- // FIXME: Do we always need SecurityImpersonation?
- Status = RtlImpersonateSelf(SecurityImpersonation);
- if (!NT_SUCCESS(Status))
- {
- ERR("RtlImpersonateSelf(SecurityImpersonation) failed, Status 0x%08x\n", Status);
- NtClose(hTokenDup);
+ Success = FALSE;
goto Quit;
}
/*
- * Attempt to acquire the process primary token assignment privilege
- * in case we actually need it.
- * The call will either succeed or fail when the caller has (or has not)
- * enough rights.
- * The last situation may not be dramatic for us. Indeed it may happen
- * that the user-provided token is a restricted version of the caller's
- * primary token (aka. a "child" token), or both tokens inherit (i.e. are
- * children, and are together "siblings") from a common parent token.
- * In this case the NT kernel allows us to assign the token to the child
- * process without the need for the assignment privilege, which is fine.
- * On the contrary, if the user-provided token is completely arbitrary,
- * then the NT kernel will enforce the presence of the assignment privilege:
- * because we failed (by assumption) to assign the privilege, the process
- * token assignment will fail as required. It is then the job of the
- * caller to manually acquire the necessary privileges.
+ * Now it's time to set the primary token into
+ * the process. On the first try, do it by
+ * impersonating the security context of the
+ * calling process (impersonate as self).
*/
- Status = RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE,
- TRUE, TRUE, &PrivilegeSet);
- HavePrivilege = NT_SUCCESS(Status);
- if (!HavePrivilege)
+ Status = InsertTokenToProcessCommon(TRUE,
+ lpProcessInformation->hProcess,
+ lpProcessInformation->hThread,
+ hTokenDup);
+ if (!NT_SUCCESS(Status))
{
- ERR("RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE) failed, Status 0x%08lx, "
- "attempting to continue without it...\n", Status);
- }
-
- AccessToken.Token = hTokenDup;
- AccessToken.Thread = lpProcessInformation->hThread;
+ /*
+ * OK, we failed. Our second (and last try) is to not
+ * impersonate as self but instead we will try by setting
+ * the original impersonation (thread) token and set the
+ * primary token to the process through this way. This is
+ * what we call -- the "rinse and repeat" approach.
+ */
+ Status = NtSetInformationThread(NtCurrentThread(),
+ ThreadImpersonationToken,
+ &OriginalImpersonationToken,
+ sizeof(OriginalImpersonationToken));
+ if (!NT_SUCCESS(Status))
+ {
+ ERR("Failed to restore impersonation token for setting process token, Status 0x%08lx\n", Status);
+ NtClose(hTokenDup);
+ Success = FALSE;
+ goto Quit;
+ }
- /* Set the new process token */
- Status = NtSetInformationProcess(lpProcessInformation->hProcess,
- ProcessAccessToken,
- (PVOID)&AccessToken,
- sizeof(AccessToken));
+ /* Retry again */
+ Status = InsertTokenToProcessCommon(FALSE,
+ lpProcessInformation->hProcess,
+ lpProcessInformation->hThread,
+ hTokenDup);
+ if (!NT_SUCCESS(Status))
+ {
+ /* Even the second try failed, bail out... */
+ ERR("Failed to insert the primary token into process, Status 0x%08lx\n", Status);
+ NtClose(hTokenDup);
+ Success = FALSE;
+ goto Quit;
+ }
- /* Restore the privilege */
- if (HavePrivilege)
- {
- RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE,
- PrivilegeSet, TRUE, &PrivilegeSet);
+ /* All good, now stop impersonation */
+ Status = NtSetInformationThread(NtCurrentThread(),
+ ThreadImpersonationToken,
+ &NullToken,
+ sizeof(NullToken));
+ if (!NT_SUCCESS(Status))
+ {
+ ERR("Failed to unset impersonationg token after setting process token, Status 0x%08lx\n", Status);
+ NtClose(hTokenDup);
+ Success = FALSE;
+ goto Quit;
+ }
}
- RevertToSelf();
+ /*
+ * FIXME: As we have successfully set up a primary token to
+ * the newly created process, we must set up as well a definite
+ * limit of quota charges for this process on the context of
+ * this user.
+ */
/* Close the duplicated token */
NtClose(hTokenDup);
+ Success = TRUE;
+ }
- /* Check whether NtSetInformationProcess() failed */
- if (!NT_SUCCESS(Status))
- {
- ERR("NtSetInformationProcess() failed, Status 0x%08x\n", Status);
- goto Quit;
- }
+ /*
+ * If the caller did not supply a token then just declare
+ * ourselves as job done. The newly created process will use
+ * the default security context at this point anyway.
+ */
+ TRACE("No token supplied, the process will use default security context!\n");
+ Success = TRUE;
- if (!NT_SUCCESS(Status))
- {
Quit:
- TerminateProcess(lpProcessInformation->hProcess, Status);
- SetLastError(RtlNtStatusToDosError(Status));
- return FALSE;
- }
+ /*
+ * If we successfully opened the thread token before
+ * and stopped the impersonation then we have to assign
+ * its original token back and close that token we have
+ * referenced it.
+ */
+ if (OriginalImpersonationToken != NULL)
+ {
+ StatusOnExit = NtSetInformationThread(NtCurrentThread(),
+ ThreadImpersonationToken,
+ &OriginalImpersonationToken,
+ sizeof(OriginalImpersonationToken));
+
+ /*
+ * We really must assert ourselves that we successfully
+ * set the original token back, otherwise if we fail
+ * then something is seriously going wrong....
+ * The status code is cached in a separate status
+ * variable because we would not want to tamper
+ * with the original status code that could have been
+ * returned by someone else above in this function code.
+ */
+ ASSERT(NT_SUCCESS(StatusOnExit));
+
+ /* De-reference it */
+ NtClose(OriginalImpersonationToken);
+ }
+
+ /* Terminate the process and set the last error status */
+ if (!NT_SUCCESS(Status))
+ {
+ TerminateProcess(lpProcessInformation->hProcess, Status);
+ SetLastError(RtlNtStatusToDosError(Status));
}
/* Resume the main thread */
@@ -222,7 +507,7 @@ Quit:
ResumeThread(lpProcessInformation->hThread);
}
- return TRUE;
+ return Success;
}
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=bf40c7a310bfd48a5e9a0…
commit bf40c7a310bfd48a5e9a0b822ab19b0d9443b788
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Mon Apr 11 20:24:41 2022 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Fri May 6 10:09:49 2022 +0200
[KERNEL32] Let KERNEL32 assign security to NLS section names
Currently Kernel32 doesn't make any server call to Basesrv in order to create NLS section names, instead it's Kernel32 itself that handles the job of NLS section names. With that said, let Kernel32 assign a security descriptor to NLS section names. See the FIXME comment on code for further dtails
---
dll/win32/kernel32/winnls/string/nls.c | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/dll/win32/kernel32/winnls/string/nls.c b/dll/win32/kernel32/winnls/string/nls.c
index 3a096884992..d810c429069 100644
--- a/dll/win32/kernel32/winnls/string/nls.c
+++ b/dll/win32/kernel32/winnls/string/nls.c
@@ -59,6 +59,9 @@ GetCPFileNameFromRegistry(UINT CodePage, LPWSTR FileName, ULONG FileNameSize);
NTSTATUS
CreateNlsDirectorySecurity(_Out_ PSECURITY_DESCRIPTOR *NlsSecurityDescriptor);
+NTSTATUS WINAPI
+CreateNlsSecurityDescriptor(_Out_ PSECURITY_DESCRIPTOR *SecurityDescriptor, _In_ SIZE_T DescriptorSize, _In_ ULONG AccessMask);
+
/* PRIVATE FUNCTIONS **********************************************************/
/**
@@ -219,6 +222,7 @@ IntGetCodePageEntry(UINT CodePage)
WCHAR FileName[MAX_PATH + 1];
UINT FileNamePos;
PCODEPAGE_ENTRY CodePageEntry;
+ PSECURITY_DESCRIPTOR NlsSd;
if (CodePage == CP_ACP)
{
return &AnsiCodePage;
@@ -281,7 +285,23 @@ IntGetCodePageEntry(UINT CodePage)
RtlInitAnsiString(&AnsiName, SectionName);
RtlAnsiStringToUnicodeString(&UnicodeName, &AnsiName, TRUE);
- InitializeObjectAttributes(&ObjectAttributes, &UnicodeName, 0, NULL, NULL);
+ /*
+ * FIXME: IntGetCodePageEntry should not create any security
+ * descriptor here but instead this responsibility should be
+ * assigned to Base Server API (aka basesrv.dll). That is,
+ * kernel32 must instruct basesrv.dll on creating NLS section
+ * names that do not exist through API message communication.
+ * However since we do not do that, let the kernel32 do the job
+ * by assigning security to NLS section names for the time being...
+ */
+ Status = CreateNlsSecurityDescriptor(&NlsSd, sizeof(SECURITY_DESCRIPTOR), SECTION_MAP_READ);
+ if (!NT_SUCCESS(Status))
+ {
+ RtlLeaveCriticalSection(&CodePageListLock);
+ return NULL;
+ }
+
+ InitializeObjectAttributes(&ObjectAttributes, &UnicodeName, 0, NULL, NlsSd);
/* Try to open the section first */
Status = NtOpenSection(&SectionHandle, SECTION_MAP_READ, &ObjectAttributes);
@@ -329,6 +349,7 @@ IntGetCodePageEntry(UINT CodePage)
}
}
RtlFreeUnicodeString(&UnicodeName);
+ HeapFree(GetProcessHeap(), 0, NlsSd);
if (!NT_SUCCESS(Status))
{
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=2092dc06bb3069155adf4…
commit 2092dc06bb3069155adf40bd7a3616fee8ec68bb
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Sat Apr 9 21:17:58 2022 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Fri May 6 10:09:49 2022 +0200
[WINLOGON][HACK] Allow network services access to default window station
HHHHHHHHHHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCCCCCCCCCCCCCCCCCCCCCCKKKKKKKKKKKKKKKKKK!!!
There are two problems concerning with network services. First, a window station should be created for every network service process that gets started although this doesn't happen. Instead, network services like RPCSS and DNS service host process (svchost.exe) attempt to access the default window station (Winsta0).
This is because the access token of these two network service processes have an authentication ID that is uniquely generated. This is incorrect, because NetworkService is a special account with its own designed authentication ID for it. As a matter of fact, no window station is created for a network service and as such
both RPCSS and DNS svchost.exe attempt to access Winsta0 which they cannot.
The second problem, albeit not quite relevant to the first one but still worth mentioning nevertheless, is that network services have an access token that is primary which it should be an impersonation token. These problems all come from LSASS as LSA infrastructure is responsible for creating access tokens with security
context for objects.
For the moment being, add a hack on Winlogon that gives allow access to the default window station to network services. When LSASS and involved components are fixed, this hack must be removed.
---
base/system/winlogon/security.c | 191 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 183 insertions(+), 8 deletions(-)
diff --git a/base/system/winlogon/security.c b/base/system/winlogon/security.c
index 26d3935fe2e..4045496abb2 100644
--- a/base/system/winlogon/security.c
+++ b/base/system/winlogon/security.c
@@ -115,7 +115,7 @@ CreateWinstaSecurity(
BOOL Success = FALSE;
SECURITY_DESCRIPTOR AbsoluteSd;
PSECURITY_DESCRIPTOR RelativeSd = NULL;
- PSID WinlogonSid = NULL, AdminsSid = NULL;
+ PSID WinlogonSid = NULL, AdminsSid = NULL, NetworkServiceSid = NULL; /* NetworkServiceSid is a HACK, see the comment below for information */
DWORD DaclSize;
PACL Dacl;
@@ -142,18 +142,62 @@ CreateWinstaSecurity(
goto Quit;
}
+ /* HACK: Create the network service SID */
+ if (!AllocateAndInitializeSid(&NtAuthority,
+ 1,
+ SECURITY_NETWORK_SERVICE_RID,
+ 0, 0, 0, 0, 0, 0, 0,
+ &NetworkServiceSid))
+ {
+ ERR("CreateWinstaSecurity(): Failed to create the network service SID (error code %lu)\n", GetLastError());
+ goto Quit;
+ }
+
/*
* Build up the DACL size. This includes a number
* of four ACEs of two different SIDs. The first two
* ACEs give both window station and generic access
* to Winlogon, the last two give limited window station
* and desktop access to admins.
+ *
+ * ===================== !!!MUST READ!!! =====================
+ *
+ * HACK -- Include in the DACL two more ACEs for network
+ * service SID. Network services will be granted full
+ * access to the default window station. Whilst technically
+ * services that are either network or local ones are part
+ * and act on behalf of the system, what we are doing here
+ * is a hack because of two reasons:
+ *
+ * 1) Winlogon does not allow default window station (Winsta0)
+ * access to network services on Windows. As a matter of fact,
+ * network services must access their own service window station
+ * (aka Service-0x0-3e4$) which never gets created. Why it never
+ * gets created is explained on the second point.
+ *
+ * 2) Our LSASS terribly lacks in code that handles special logon
+ * service types, NetworkService and LocalService. For this reason
+ * whenever an access token is created for a network service process
+ * for example, its authentication ID (aka LogonId represented as a LUID)
+ * is a uniquely generated ID by LSASS for this process. This is wrong
+ * on so many levels, partly because a network service is not a regular
+ * service and network services have their own special authentication logon
+ * ID (with its respective LUID as {0x3e4, 0x0}). On top of that, a network
+ * service process must have an impersonation token but for whatever reason
+ * we are creating a primary access token instead.
+ *
+ * FOR ANYONE WHO'S INTERESTED ON FIXING THIS, DO NOT FORGET TO REMOVE THIS
+ * HACK!!!
+ *
+ * =========================== !!!END!!! ================================
*/
DaclSize = sizeof(ACL) +
sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(WinlogonSid) +
sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(WinlogonSid) +
sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(AdminsSid) +
- sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(AdminsSid);
+ sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(AdminsSid) +
+ sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(NetworkServiceSid) +
+ sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(NetworkServiceSid);
/* Allocate the DACL now */
Dacl = RtlAllocateHeap(RtlGetProcessHeap(),
@@ -216,6 +260,28 @@ CreateWinstaSecurity(
goto Quit;
}
+ /* HACK: Fifth ACE -- give full access to network services */
+ if (!AddAccessAllowedAceEx(Dacl,
+ ACL_REVISION,
+ NO_PROPAGATE_INHERIT_ACE,
+ WINSTA_ALL,
+ NetworkServiceSid))
+ {
+ ERR("CreateWinstaSecurity(): Failed to set ACE for network service (error code %lu)\n", GetLastError());
+ goto Quit;
+ }
+
+ /* HACK: Sixth ACE -- give full generic access to network services */
+ if (!AddAccessAllowedAceEx(Dacl,
+ ACL_REVISION,
+ INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE,
+ GENERIC_ACCESS,
+ NetworkServiceSid))
+ {
+ ERR("CreateWinstaSecurity(): Failed to set ACE for network service (error code %lu)\n", GetLastError());
+ goto Quit;
+ }
+
/* Initialize the security descriptor */
if (!InitializeSecurityDescriptor(&AbsoluteSd, SECURITY_DESCRIPTOR_REVISION))
{
@@ -253,6 +319,13 @@ Quit:
FreeSid(AdminsSid);
}
+ /* HACK */
+ if (NetworkServiceSid != NULL)
+ {
+ FreeSid(NetworkServiceSid);
+ }
+ /* END HACK */
+
if (Dacl != NULL)
{
RtlFreeHeap(RtlGetProcessHeap(), 0, Dacl);
@@ -289,7 +362,7 @@ CreateApplicationDesktopSecurity(
BOOL Success = FALSE;
SECURITY_DESCRIPTOR AbsoluteSd;
PSECURITY_DESCRIPTOR RelativeSd = NULL;
- PSID WinlogonSid = NULL, AdminsSid = NULL;
+ PSID WinlogonSid = NULL, AdminsSid = NULL, NetworkServiceSid = NULL; /* NetworkServiceSid is a HACK, see the comment in CreateWinstaSecurity for information */
DWORD DaclSize;
PACL Dacl;
@@ -316,6 +389,17 @@ CreateApplicationDesktopSecurity(
goto Quit;
}
+ /* HACK: Create the network service SID */
+ if (!AllocateAndInitializeSid(&NtAuthority,
+ 1,
+ SECURITY_NETWORK_SERVICE_RID,
+ 0, 0, 0, 0, 0, 0, 0,
+ &NetworkServiceSid))
+ {
+ ERR("CreateApplicationDesktopSecurity(): Failed to create the network service SID (error code %lu)\n", GetLastError());
+ goto Quit;
+ }
+
/*
* Build up the DACL size. This includes a number
* of two ACEs of two different SIDs. The first ACE
@@ -324,7 +408,8 @@ CreateApplicationDesktopSecurity(
*/
DaclSize = sizeof(ACL) +
sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(WinlogonSid) +
- sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(AdminsSid);
+ sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(AdminsSid) +
+ sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(NetworkServiceSid); /* HACK */
/* Allocate the DACL now */
Dacl = RtlAllocateHeap(RtlGetProcessHeap(),
@@ -365,6 +450,17 @@ CreateApplicationDesktopSecurity(
goto Quit;
}
+ /* HACK: Third ACE -- Give full desktop power to network services */
+ if (!AddAccessAllowedAceEx(Dacl,
+ ACL_REVISION,
+ 0,
+ DESKTOP_ALL,
+ NetworkServiceSid))
+ {
+ ERR("CreateApplicationDesktopSecurity(): Failed to set ACE for network services (error code %lu)\n", GetLastError());
+ goto Quit;
+ }
+
/* Initialize the security descriptor */
if (!InitializeSecurityDescriptor(&AbsoluteSd, SECURITY_DESCRIPTOR_REVISION))
{
@@ -402,6 +498,13 @@ Quit:
FreeSid(AdminsSid);
}
+ /* HACK */
+ if (NetworkServiceSid != NULL)
+ {
+ FreeSid(NetworkServiceSid);
+ }
+ /* END HACK */
+
if (Dacl != NULL)
{
RtlFreeHeap(RtlGetProcessHeap(), 0, Dacl);
@@ -775,7 +878,7 @@ AllowWinstaAccessToUser(
BOOL Success = FALSE;
SECURITY_DESCRIPTOR AbsoluteSd;
PSECURITY_DESCRIPTOR RelativeSd = NULL;
- PSID WinlogonSid = NULL, AdminsSid = NULL, InteractiveSid = NULL;
+ PSID WinlogonSid = NULL, AdminsSid = NULL, InteractiveSid = NULL, NetworkServiceSid = NULL; /* NetworkServiceSid is a HACK, see the comment in CreateWinstaSecurity for information */
SECURITY_INFORMATION SecurityInformation;
DWORD DaclSize;
PACL Dacl;
@@ -814,6 +917,17 @@ AllowWinstaAccessToUser(
goto Quit;
}
+ /* HACK: Create the network service SID */
+ if (!AllocateAndInitializeSid(&NtAuthority,
+ 1,
+ SECURITY_NETWORK_SERVICE_RID,
+ 0, 0, 0, 0, 0, 0, 0,
+ &NetworkServiceSid))
+ {
+ ERR("AllowWinstaAccessToUser(): Failed to create the network service SID (error code %lu)\n", GetLastError());
+ goto Quit;
+ }
+
/*
* Build up the DACL size. This includes a number
* of eight ACEs of four different SIDs. The first ACE
@@ -830,7 +944,9 @@ AllowWinstaAccessToUser(
sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(InteractiveSid) +
sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(InteractiveSid) +
sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(LogonSid) +
- sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(LogonSid);
+ sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(LogonSid) +
+ sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(NetworkServiceSid) + /* HACK */
+ sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(NetworkServiceSid);
/* Allocate the DACL now */
Dacl = RtlAllocateHeap(RtlGetProcessHeap(),
@@ -937,6 +1053,28 @@ AllowWinstaAccessToUser(
goto Quit;
}
+ /* HACK : Ninenth ACE -- Give full winsta access to network services */
+ if (!AddAccessAllowedAceEx(Dacl,
+ ACL_REVISION,
+ NO_PROPAGATE_INHERIT_ACE,
+ WINSTA_ALL,
+ NetworkServiceSid))
+ {
+ ERR("AllowWinstaAccessToUser(): Failed to set ACE for logon network service SID (error code %lu)\n", GetLastError());
+ goto Quit;
+ }
+
+ /* HACK: Tenth ACE -- Give generic access to network services */
+ if (!AddAccessAllowedAceEx(Dacl,
+ ACL_REVISION,
+ INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE,
+ GENERIC_ACCESS,
+ NetworkServiceSid))
+ {
+ ERR("AllowWinstaAccessToUser(): Failed to set ACE for network service SID (error code %lu)\n", GetLastError());
+ goto Quit;
+ }
+
/* Initialize the security descriptor */
if (!InitializeSecurityDescriptor(&AbsoluteSd, SECURITY_DESCRIPTOR_REVISION))
{
@@ -985,6 +1123,13 @@ Quit:
FreeSid(InteractiveSid);
}
+ /* HACK */
+ if (NetworkServiceSid != NULL)
+ {
+ FreeSid(NetworkServiceSid);
+ }
+ /* END HACK */
+
if (Dacl != NULL)
{
RtlFreeHeap(RtlGetProcessHeap(), 0, Dacl);
@@ -1024,7 +1169,7 @@ AllowDesktopAccessToUser(
BOOL Success = FALSE;
SECURITY_DESCRIPTOR AbsoluteSd;
PSECURITY_DESCRIPTOR RelativeSd = NULL;
- PSID WinlogonSid = NULL, AdminsSid = NULL, InteractiveSid = NULL;
+ PSID WinlogonSid = NULL, AdminsSid = NULL, InteractiveSid = NULL, NetworkServiceSid = NULL; /* NetworkServiceSid is a HACK, see the comment in CreateWinstaSecurity for information */
SECURITY_INFORMATION SecurityInformation;
DWORD DaclSize;
PACL Dacl;
@@ -1063,6 +1208,17 @@ AllowDesktopAccessToUser(
goto Quit;
}
+ /* HACK: Create the network service SID */
+ if (!AllocateAndInitializeSid(&NtAuthority,
+ 1,
+ SECURITY_NETWORK_SERVICE_RID,
+ 0, 0, 0, 0, 0, 0, 0,
+ &NetworkServiceSid))
+ {
+ ERR("AllowDesktopAccessToUser(): Failed to create the network service SID (error code %lu)\n", GetLastError());
+ goto Quit;
+ }
+
/*
* Build up the DACL size. This includes a number
* of four ACEs of four different SIDs. The first ACE
@@ -1075,7 +1231,8 @@ AllowDesktopAccessToUser(
sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(WinlogonSid) +
sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(AdminsSid) +
sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(InteractiveSid) +
- sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(LogonSid);
+ sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(LogonSid) +
+ sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(NetworkServiceSid); /* HACK */
/* Allocate the DACL now */
Dacl = RtlAllocateHeap(RtlGetProcessHeap(),
@@ -1138,6 +1295,17 @@ AllowDesktopAccessToUser(
goto Quit;
}
+ /* HACK: Fifth ACE -- Give full desktop to network services */
+ if (!AddAccessAllowedAceEx(Dacl,
+ ACL_REVISION,
+ 0,
+ DESKTOP_ALL,
+ NetworkServiceSid))
+ {
+ ERR("AllowDesktopAccessToUser(): Failed to set ACE for network service SID (error code %lu)\n", GetLastError());
+ goto Quit;
+ }
+
/* Initialize the security descriptor */
if (!InitializeSecurityDescriptor(&AbsoluteSd, SECURITY_DESCRIPTOR_REVISION))
{
@@ -1186,6 +1354,13 @@ Quit:
FreeSid(InteractiveSid);
}
+ /* HACK */
+ if (NetworkServiceSid != NULL)
+ {
+ FreeSid(NetworkServiceSid);
+ }
+ /* END HACK */
+
if (Dacl != NULL)
{
RtlFreeHeap(RtlGetProcessHeap(), 0, Dacl);
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=65a72fb527a2f8e0b5bef…
commit 65a72fb527a2f8e0b5bef3aad8f46ebaa2a526fd
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Sun Mar 27 20:01:19 2022 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Fri May 6 10:09:48 2022 +0200
[USER32] Capture the security descriptor from the caller when creating a window station
When creating a window station with CreateWindowStationW, the function ignores the security descriptor provided by the caller and instead it uses whatever descriptor the system can find.
---
win32ss/user/user32/misc/winsta.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/win32ss/user/user32/misc/winsta.c b/win32ss/user/user32/misc/winsta.c
index 7308ad14619..111d7dec37a 100644
--- a/win32ss/user/user32/misc/winsta.c
+++ b/win32ss/user/user32/misc/winsta.c
@@ -104,7 +104,7 @@ CreateWindowStationW(
lpwinsta ? &WindowStationName : NULL,
OBJ_CASE_INSENSITIVE | OBJ_OPENIF,
hWindowStationsDir,
- NULL);
+ lpsa ? lpsa->lpSecurityDescriptor : NULL);
/* Check if the handle should be inheritable */
if (lpsa && lpsa->bInheritHandle)
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=878c2f4444445c66aa8bb…
commit 878c2f4444445c66aa8bb52b0baaf7f0f9c0d383
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Wed Mar 16 21:03:56 2022 +0100
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Fri May 6 10:09:48 2022 +0200
[WIN32K:NTUSER] Implement security infrastructure for NTUSER component
Implement a base security infrastructure with code that sets up a security descriptor for the service that we're going to connect through it. Such service is based upon a desktop and a window station.
=== DOCUMENTATION REMARKS ===
The authenticated user, represented by an access token that describes its security context, is the main holder and has ultimate power against the default created desktop and window station objects in USER. The authenticated user in question
is the actual logged in user, this is the case when the server is impersonating a client. Administrators on the other hand have some share of power against default desktop but their power in question is extremely limited against the default
window station as admins can only just enumerate the available and valid handle stations within a desktop.
---
win32ss/CMakeLists.txt | 1 +
win32ss/gdi/ntgdi/gdidbg.c | 1 +
win32ss/pch.h | 1 +
win32ss/user/ntuser/desktop.c | 30 ++-
win32ss/user/ntuser/desktop.h | 26 --
win32ss/user/ntuser/security.c | 493 ++++++++++++++++++++++++++++++++++++++
win32ss/user/ntuser/security.h | 92 +++++++
win32ss/user/ntuser/win32kdebug.h | 1 +
win32ss/user/ntuser/winsta.h | 27 +--
win32ss/win32kp.h | 1 +
10 files changed, 617 insertions(+), 56 deletions(-)
diff --git a/win32ss/CMakeLists.txt b/win32ss/CMakeLists.txt
index a5efa7f9f16..0a7dd1492de 100644
--- a/win32ss/CMakeLists.txt
+++ b/win32ss/CMakeLists.txt
@@ -144,6 +144,7 @@ list(APPEND SOURCE
user/ntuser/prop.c
user/ntuser/scrollbar.c
user/ntuser/scrollex.c
+ user/ntuser/security.c
user/ntuser/session.c
user/ntuser/shutdown.c
user/ntuser/simplecall.c
diff --git a/win32ss/gdi/ntgdi/gdidbg.c b/win32ss/gdi/ntgdi/gdidbg.c
index 51de52670b8..2dbe30e69d5 100644
--- a/win32ss/gdi/ntgdi/gdidbg.c
+++ b/win32ss/gdi/ntgdi/gdidbg.c
@@ -87,6 +87,7 @@ DBG_CHANNEL DbgChannels[DbgChCount] = {
{L"UserProcess", DbgChUserProcess},
{L"UserProp", DbgChUserProp},
{L"UserScrollbar", DbgChUserScrollbar},
+ {L"UserSecurity", DbgChUserSecurity},
{L"UserShutdown", DbgChUserShutdown},
{L"UserSysparams", DbgChUserSysparams},
{L"UserTimer", DbgChUserTimer},
diff --git a/win32ss/pch.h b/win32ss/pch.h
index 40fd8a7f861..60bdb37a8d6 100644
--- a/win32ss/pch.h
+++ b/win32ss/pch.h
@@ -25,6 +25,7 @@
#include <ndk/mmfuncs.h>
#include <ndk/obfuncs.h>
#include <ndk/psfuncs.h>
+#include <ndk/sefuncs.h>
#include <ndk/rtlfuncs.h>
#include <ntstrsafe.h>
#include <ntintsafe.h>
diff --git a/win32ss/user/ntuser/desktop.c b/win32ss/user/ntuser/desktop.c
index 4b82da31ce9..fb8d6ed96cd 100644
--- a/win32ss/user/ntuser/desktop.c
+++ b/win32ss/user/ntuser/desktop.c
@@ -555,6 +555,7 @@ IntResolveDesktop(
LUID ProcessLuid;
USHORT StrSize;
SIZE_T MemSize;
+ PSECURITY_DESCRIPTOR ServiceSD = NULL;
POBJECT_ATTRIBUTES ObjectAttributes = NULL;
PUNICODE_STRING ObjectName;
UNICODE_STRING WinStaName, DesktopName;
@@ -1012,16 +1013,29 @@ IntResolveDesktop(
}
ObjectName->Length = (USHORT)(wcslen(ObjectName->Buffer) * sizeof(WCHAR));
+ /*
+ * Set up a security descriptor for the service.
+ * A service is generally based upon a desktop
+ * and a window station. The newly created window
+ * station and desktop will get this security descriptor
+ * if such objects weren't created before.
+ */
+ Status = IntCreateServiceSecurity(&ServiceSD);
+ if (!NT_SUCCESS(Status))
+ {
+ ERR("Failed to create a security descriptor for default window station, Status 0x%08lx\n", Status);
+ goto Quit;
+ }
+
/*
* Create or open the non-interactive window station.
* NOTE: The non-interactive window station handle is never inheritable.
*/
- // FIXME: Set security!
InitializeObjectAttributes(ObjectAttributes,
ObjectName,
OBJ_CASE_INSENSITIVE | OBJ_OPENIF,
NULL,
- NULL);
+ ServiceSD);
Status = IntCreateWindowStation(&hWinSta,
ObjectAttributes,
@@ -1054,8 +1068,11 @@ IntResolveDesktop(
}
ObjectName->Length = (USHORT)(wcslen(ObjectName->Buffer) * sizeof(WCHAR));
- /* NOTE: The non-interactive desktop handle is never inheritable. */
- // FIXME: Set security!
+ /*
+ * NOTE: The non-interactive desktop handle is never inheritable.
+ * The security descriptor is inherited from the newly created
+ * window station for the desktop.
+ */
InitializeObjectAttributes(ObjectAttributes,
ObjectName,
OBJ_CASE_INSENSITIVE | OBJ_OPENIF,
@@ -1175,6 +1192,8 @@ Quit:
{
*phWinSta = hWinSta;
*phDesktop = hDesktop;
+
+ IntFreeSecurityBuffer(ServiceSD);
return STATUS_SUCCESS;
}
else
@@ -1191,6 +1210,9 @@ Quit:
if (hWinSta)
ObCloseHandle(hWinSta, UserMode);
+ if (ServiceSD)
+ IntFreeSecurityBuffer(ServiceSD);
+
SetLastNtError(Status);
return Status;
}
diff --git a/win32ss/user/ntuser/desktop.h b/win32ss/user/ntuser/desktop.h
index 5a7f4bca500..169cca1819e 100644
--- a/win32ss/user/ntuser/desktop.h
+++ b/win32ss/user/ntuser/desktop.h
@@ -55,32 +55,6 @@ typedef struct _DESKTOP
#define DT_GWL_PROCESSID 0
#define DT_GWL_THREADID 4
-#define DESKTOP_READ STANDARD_RIGHTS_READ | \
- DESKTOP_ENUMERATE | \
- DESKTOP_READOBJECTS
-
-#define DESKTOP_WRITE STANDARD_RIGHTS_WRITE | \
- DESKTOP_CREATEMENU | \
- DESKTOP_CREATEWINDOW | \
- DESKTOP_HOOKCONTROL | \
- DESKTOP_JOURNALPLAYBACK | \
- DESKTOP_JOURNALRECORD | \
- DESKTOP_WRITEOBJECTS
-
-#define DESKTOP_EXECUTE STANDARD_RIGHTS_EXECUTE | \
- DESKTOP_SWITCHDESKTOP
-
-#define DESKTOP_ALL_ACCESS STANDARD_RIGHTS_REQUIRED | \
- DESKTOP_CREATEMENU | \
- DESKTOP_CREATEWINDOW | \
- DESKTOP_ENUMERATE | \
- DESKTOP_HOOKCONTROL | \
- DESKTOP_JOURNALPLAYBACK | \
- DESKTOP_JOURNALRECORD | \
- DESKTOP_READOBJECTS | \
- DESKTOP_SWITCHDESKTOP | \
- DESKTOP_WRITEOBJECTS
-
extern PDESKTOP gpdeskInputDesktop;
extern PCLS DesktopWindowClass;
extern HDC ScreenDeviceContext;
diff --git a/win32ss/user/ntuser/security.c b/win32ss/user/ntuser/security.c
new file mode 100644
index 00000000000..0286a76f5e3
--- /dev/null
+++ b/win32ss/user/ntuser/security.c
@@ -0,0 +1,493 @@
+/*
+ * 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(a)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 */
diff --git a/win32ss/user/ntuser/security.h b/win32ss/user/ntuser/security.h
new file mode 100644
index 00000000000..f719beccc8a
--- /dev/null
+++ b/win32ss/user/ntuser/security.h
@@ -0,0 +1,92 @@
+/*
+ * 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(a)reactos.org>
+ */
+
+#pragma once
+
+//
+// USER objects security rights
+//
+
+/* Desktop access rights */
+#define DESKTOP_READ (STANDARD_RIGHTS_READ | \
+ DESKTOP_ENUMERATE | \
+ DESKTOP_READOBJECTS)
+
+#define DESKTOP_WRITE (STANDARD_RIGHTS_WRITE | \
+ DESKTOP_CREATEMENU | \
+ DESKTOP_CREATEWINDOW | \
+ DESKTOP_HOOKCONTROL | \
+ DESKTOP_JOURNALPLAYBACK | \
+ DESKTOP_JOURNALRECORD | \
+ DESKTOP_WRITEOBJECTS)
+
+#define DESKTOP_EXECUTE (STANDARD_RIGHTS_EXECUTE | \
+ DESKTOP_SWITCHDESKTOP)
+
+#define DESKTOP_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | \
+ DESKTOP_CREATEMENU | \
+ DESKTOP_CREATEWINDOW | \
+ DESKTOP_ENUMERATE | \
+ DESKTOP_HOOKCONTROL | \
+ DESKTOP_JOURNALPLAYBACK | \
+ DESKTOP_JOURNALRECORD | \
+ DESKTOP_READOBJECTS | \
+ DESKTOP_SWITCHDESKTOP | \
+ DESKTOP_WRITEOBJECTS)
+
+/* Window Station access rights */
+#define WINSTA_READ (STANDARD_RIGHTS_READ | \
+ WINSTA_ENUMDESKTOPS | \
+ WINSTA_ENUMERATE | \
+ WINSTA_READATTRIBUTES | \
+ WINSTA_READSCREEN)
+
+#define WINSTA_WRITE (STANDARD_RIGHTS_WRITE | \
+ WINSTA_ACCESSCLIPBOARD | \
+ WINSTA_CREATEDESKTOP | \
+ WINSTA_WRITEATTRIBUTES)
+
+#define WINSTA_EXECUTE (STANDARD_RIGHTS_EXECUTE | \
+ WINSTA_ACCESSGLOBALATOMS | \
+ WINSTA_EXITWINDOWS)
+
+#define WINSTA_ACCESS_ALL (STANDARD_RIGHTS_REQUIRED | \
+ WINSTA_ACCESSCLIPBOARD | \
+ WINSTA_ACCESSGLOBALATOMS | \
+ WINSTA_CREATEDESKTOP | \
+ WINSTA_ENUMDESKTOPS | \
+ WINSTA_ENUMERATE | \
+ WINSTA_EXITWINDOWS | \
+ WINSTA_READATTRIBUTES | \
+ WINSTA_READSCREEN | \
+ WINSTA_WRITEATTRIBUTES)
+
+//
+// Function prototypes
+//
+
+HANDLE
+IntCaptureCurrentAccessToken(VOID);
+
+PVOID
+IntAllocateSecurityBuffer(
+ _In_ SIZE_T Length);
+
+VOID
+IntFreeSecurityBuffer(
+ _In_ PVOID Buffer);
+
+NTSTATUS
+IntQueryUserSecurityIdentification(
+ _Out_ PTOKEN_USER *User);
+
+NTSTATUS
+NTAPI
+IntCreateServiceSecurity(
+ _Out_ PSECURITY_DESCRIPTOR *ServiceSd);
+
+/* EOF */
diff --git a/win32ss/user/ntuser/win32kdebug.h b/win32ss/user/ntuser/win32kdebug.h
index 48175854401..2d9e1a95351 100644
--- a/win32ss/user/ntuser/win32kdebug.h
+++ b/win32ss/user/ntuser/win32kdebug.h
@@ -108,6 +108,7 @@
DbgChUserProcess,
DbgChUserProp,
DbgChUserScrollbar,
+ DbgChUserSecurity,
DbgChUserShutdown,
DbgChUserSysparams,
DbgChUserThread,
diff --git a/win32ss/user/ntuser/winsta.h b/win32ss/user/ntuser/winsta.h
index 34720ad5093..b36aaa1ce55 100644
--- a/win32ss/user/ntuser/winsta.h
+++ b/win32ss/user/ntuser/winsta.h
@@ -47,32 +47,6 @@ extern HANDLE gpidLogon;
extern HWND hwndSAS;
extern UNICODE_STRING gustrWindowStationsDir;
-#define WINSTA_READ STANDARD_RIGHTS_READ | \
- WINSTA_ENUMDESKTOPS | \
- WINSTA_ENUMERATE | \
- WINSTA_READATTRIBUTES | \
- WINSTA_READSCREEN
-
-#define WINSTA_WRITE STANDARD_RIGHTS_WRITE | \
- WINSTA_ACCESSCLIPBOARD | \
- WINSTA_CREATEDESKTOP | \
- WINSTA_WRITEATTRIBUTES
-
-#define WINSTA_EXECUTE STANDARD_RIGHTS_EXECUTE | \
- WINSTA_ACCESSGLOBALATOMS | \
- WINSTA_EXITWINDOWS
-
-#define WINSTA_ACCESS_ALL STANDARD_RIGHTS_REQUIRED | \
- WINSTA_ACCESSCLIPBOARD | \
- WINSTA_ACCESSGLOBALATOMS | \
- WINSTA_CREATEDESKTOP | \
- WINSTA_ENUMDESKTOPS | \
- WINSTA_ENUMERATE | \
- WINSTA_EXITWINDOWS | \
- WINSTA_READATTRIBUTES | \
- WINSTA_READSCREEN | \
- WINSTA_WRITEATTRIBUTES
-
CODE_SEG("INIT")
NTSTATUS
NTAPI
@@ -125,4 +99,5 @@ BOOL FASTCALL UserSetProcessWindowStation(HWINSTA hWindowStation);
BOOL FASTCALL co_IntInitializeDesktopGraphics(VOID);
VOID FASTCALL IntEndDesktopGraphics(VOID);
BOOL FASTCALL CheckWinstaAttributeAccess(ACCESS_MASK);
+
/* EOF */
diff --git a/win32ss/win32kp.h b/win32ss/win32kp.h
index e8f401d35c2..9a6ae760005 100644
--- a/win32ss/win32kp.h
+++ b/win32ss/win32kp.h
@@ -81,6 +81,7 @@ typedef struct _DC *PDC;
#include "user/ntuser/painting.h"
#include "user/ntuser/class.h"
#include "user/ntuser/window.h"
+#include "user/ntuser/security.h"
#include "user/ntuser/sysparams.h"
#include "user/ntuser/prop.h"
#include "user/ntuser/guicheck.h"