https://git.reactos.org/?p=reactos.git;a=commitdiff;h=ae8ebe45d20b933a5890a…
commit ae8ebe45d20b933a5890afa65e3e8d7e5c2f8a54
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Sun May 2 20:45:57 2021 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun May 2 20:45:57 2021 +0200
[NTDLL_APITEST] Implement alignment probing library code
The probing library code only probes data types for threads/processes information classes in Process Structure subsystem for now.
---
modules/rostests/apitests/ntdll/precomp.h | 23 +++
modules/rostests/apitests/ntdll/probelib.c | 320 +++++++++++++++++++++++++++++
2 files changed, 343 insertions(+)
diff --git a/modules/rostests/apitests/ntdll/precomp.h b/modules/rostests/apitests/ntdll/precomp.h
index 82d3e0733b0..d476f1cc958 100644
--- a/modules/rostests/apitests/ntdll/precomp.h
+++ b/modules/rostests/apitests/ntdll/precomp.h
@@ -12,4 +12,27 @@
#include <ndk/ntndk.h>
#include <strsafe.h>
+/* probelib.c */
+typedef enum _ALIGNMENT_PROBE_MODE
+{
+ QUERY,
+ SET
+} ALIGNMENT_PROBE_MODE;
+
+VOID
+QuerySetProcessValidator(
+ _In_ ALIGNMENT_PROBE_MODE ValidationMode,
+ _In_ ULONG InfoClassIndex,
+ _In_ PVOID InfoPointer,
+ _In_ ULONG InfoLength,
+ _In_ NTSTATUS ExpectedStatus);
+
+VOID
+QuerySetThreadValidator(
+ _In_ ALIGNMENT_PROBE_MODE ValidationMode,
+ _In_ ULONG InfoClassIndex,
+ _In_ PVOID InfoPointer,
+ _In_ ULONG InfoLength,
+ _In_ NTSTATUS ExpectedStatus);
+
#endif /* _NTDLL_APITEST_PRECOMP_H_ */
diff --git a/modules/rostests/apitests/ntdll/probelib.c b/modules/rostests/apitests/ntdll/probelib.c
new file mode 100644
index 00000000000..e59b1f5284f
--- /dev/null
+++ b/modules/rostests/apitests/ntdll/probelib.c
@@ -0,0 +1,320 @@
+/*
+ * PROJECT: ReactOS API Tests
+ * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE: Small library with probing utilities for thread/process classes information
+ * COPYRIGHT: Copyright 2020 George Bișoc <george.bisoc(a)reactos.org>
+ */
+
+#include "precomp.h"
+#include <internal/ps_i.h>
+
+VOID
+QuerySetProcessValidator(
+ _In_ ALIGNMENT_PROBE_MODE ValidationMode,
+ _In_ ULONG InfoClassIndex,
+ _In_ PVOID InfoPointer,
+ _In_ ULONG InfoLength,
+ _In_ NTSTATUS ExpectedStatus)
+{
+ NTSTATUS Status, SpecialStatus = STATUS_SUCCESS;
+
+ /* Before doing anything, check if we want query or set validation */
+ switch (ValidationMode)
+ {
+ case QUERY:
+ {
+ switch (InfoClassIndex)
+ {
+ case ProcessWorkingSetWatch:
+ {
+ SpecialStatus = STATUS_UNSUCCESSFUL;
+ break;
+ }
+
+ case ProcessHandleTracing:
+ {
+ SpecialStatus = STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ /*
+ * This class returns an arbitrary size pointed by InformationLength
+ * which equates to the image filename of the process. Such status
+ * is returned in an invalid address query (STATUS_ACCESS_VIOLATION)
+ * where the function expects STATUS_INFO_LENGTH_MISMATCH instead.
+ */
+ case ProcessImageFileName:
+ {
+ SpecialStatus = STATUS_INFO_LENGTH_MISMATCH;
+ break;
+ }
+
+ /* These classes don't belong in the query group */
+ case ProcessBasePriority:
+ case ProcessRaisePriority:
+ case ProcessExceptionPort:
+ case ProcessAccessToken:
+ case ProcessLdtSize:
+ case ProcessIoPortHandlers:
+ case ProcessUserModeIOPL:
+ case ProcessEnableAlignmentFaultFixup:
+ case ProcessAffinityMask:
+ case ProcessForegroundInformation:
+ {
+ SpecialStatus = STATUS_INVALID_INFO_CLASS;
+ break;
+ }
+
+ /* These classes don't exist in Server 2003 */
+ case ProcessIoPriority:
+ case ProcessTlsInformation:
+ case ProcessCycleTime:
+ case ProcessPagePriority:
+ case ProcessInstrumentationCallback:
+ case ProcessThreadStackAllocation:
+ case ProcessWorkingSetWatchEx:
+ case ProcessImageFileNameWin32:
+ case ProcessImageFileMapping:
+ case ProcessAffinityUpdateMode:
+ case ProcessMemoryAllocationMode:
+ {
+ SpecialStatus = STATUS_INVALID_INFO_CLASS;
+ break;
+ }
+ }
+
+ /* Query the information */
+ Status = NtQueryInformationProcess(NtCurrentProcess(),
+ InfoClassIndex,
+ InfoPointer,
+ InfoLength,
+ NULL);
+
+ /* And probe the results we've got */
+ ok(Status == ExpectedStatus || Status == SpecialStatus || Status == STATUS_DATATYPE_MISALIGNMENT,
+ "0x%lx or special status (0x%lx) expected but got 0x%lx for class information %lu in query information process operation!\n", ExpectedStatus, SpecialStatus, Status, InfoClassIndex);
+ break;
+ }
+
+ case SET:
+ {
+ switch (InfoClassIndex)
+ {
+ case ProcessIoPortHandlers:
+ {
+ SpecialStatus = STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ /*
+ * This class returns STATUS_SUCCESS when testing
+ * for STATUS_ACCESS_VIOLATION (setting an invalid address).
+ */
+ case ProcessWorkingSetWatch:
+ {
+ SpecialStatus = STATUS_PORT_ALREADY_SET;
+ break;
+ }
+
+ case ProcessUserModeIOPL:
+ {
+ SpecialStatus = STATUS_PRIVILEGE_NOT_HELD;
+ break;
+ }
+
+ /* These classes don't belong in the set group */
+ case ProcessBasicInformation:
+ case ProcessIoCounters:
+ case ProcessVmCounters:
+ case ProcessTimes:
+ case ProcessDebugPort:
+ case ProcessPooledUsageAndLimits:
+ case ProcessHandleCount:
+ case ProcessWow64Information:
+ case ProcessImageFileName:
+ case ProcessLUIDDeviceMapsEnabled:
+ case ProcessDebugObjectHandle:
+ case ProcessCookie:
+ case ProcessImageInformation:
+ {
+ SpecialStatus = STATUS_INVALID_INFO_CLASS;
+ break;
+ }
+
+ /* These classes don't exist in Server 2003 */
+ case ProcessIoPriority:
+ case ProcessTlsInformation:
+ case ProcessCycleTime:
+ case ProcessPagePriority:
+ case ProcessInstrumentationCallback:
+ case ProcessThreadStackAllocation:
+ case ProcessWorkingSetWatchEx:
+ case ProcessImageFileNameWin32:
+ case ProcessImageFileMapping:
+ case ProcessAffinityUpdateMode:
+ case ProcessMemoryAllocationMode:
+ {
+ SpecialStatus = STATUS_INVALID_INFO_CLASS;
+ break;
+ }
+
+ /* Alignment probing is not performed for these classes */
+ case ProcessEnableAlignmentFaultFixup:
+ case ProcessPriorityClass:
+ case ProcessForegroundInformation:
+ {
+ SpecialStatus = STATUS_ACCESS_VIOLATION;
+ break;
+ }
+ }
+
+ /* Set the information */
+ Status = NtSetInformationProcess(NtCurrentProcess(),
+ InfoClassIndex,
+ InfoPointer,
+ InfoLength);
+
+ /* And probe the results we've got */
+ ok(Status == ExpectedStatus || Status == SpecialStatus || Status == STATUS_DATATYPE_MISALIGNMENT || Status == STATUS_SUCCESS,
+ "0x%lx or special status (0x%lx) expected but got 0x%lx for class information %lu in set information process operation!\n", ExpectedStatus, SpecialStatus, Status, InfoClassIndex);
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+VOID
+QuerySetThreadValidator(
+ _In_ ALIGNMENT_PROBE_MODE ValidationMode,
+ _In_ ULONG InfoClassIndex,
+ _In_ PVOID InfoPointer,
+ _In_ ULONG InfoLength,
+ _In_ NTSTATUS ExpectedStatus)
+{
+ NTSTATUS Status, SpecialStatus = STATUS_SUCCESS;
+
+ /* Before doing anything, check if we want query or set validation */
+ switch (ValidationMode)
+ {
+ case QUERY:
+ {
+ switch (InfoClassIndex)
+ {
+ /* These classes don't belong in the query group */
+ case ThreadPriority:
+ case ThreadBasePriority:
+ case ThreadAffinityMask:
+ case ThreadImpersonationToken:
+ case ThreadEnableAlignmentFaultFixup:
+ case ThreadZeroTlsCell:
+ case ThreadIdealProcessor:
+ case ThreadSetTlsArrayAddress:
+ case ThreadHideFromDebugger:
+ case ThreadSwitchLegacyState:
+ {
+ SpecialStatus = STATUS_INVALID_INFO_CLASS;
+ break;
+ }
+
+ /* These classes don't exist in Server 2003 SP2 */
+ case ThreadEventPair_Reusable:
+ case ThreadLastSystemCall:
+ case ThreadIoPriority:
+ case ThreadCycleTime:
+ case ThreadPagePriority:
+ case ThreadActualBasePriority:
+ case ThreadTebInformation:
+ case ThreadCSwitchMon:
+ {
+ SpecialStatus = STATUS_INVALID_INFO_CLASS;
+ break;
+ }
+ }
+
+ /* Query the information */
+ Status = NtQueryInformationThread(NtCurrentThread(),
+ InfoClassIndex,
+ InfoPointer,
+ InfoLength,
+ NULL);
+
+ /* And probe the results we've got */
+ ok(Status == ExpectedStatus || Status == SpecialStatus || Status == STATUS_DATATYPE_MISALIGNMENT,
+ "0x%lx or special status (0x%lx) expected but got 0x%lx for class information %lu in query information thread operation!\n", ExpectedStatus, SpecialStatus, Status, InfoClassIndex);
+ break;
+ }
+
+ case SET:
+ {
+ switch (InfoClassIndex)
+ {
+ /* This class is not implemented in Windows Server 2003 SP2 */
+ case ThreadSwitchLegacyState:
+ {
+ SpecialStatus = STATUS_NOT_IMPLEMENTED;
+ break;
+ }
+
+ /*
+ * This class doesn't take a strict type for size length.
+ * The function happily succeds on an information length
+ * mismatch scenario with STATUS_SUCCESS.
+ */
+ case ThreadHideFromDebugger:
+ {
+ SpecialStatus = STATUS_INFO_LENGTH_MISMATCH;
+ break;
+ }
+
+ /* These classes don't belong in the set group */
+ case ThreadBasicInformation:
+ case ThreadTimes:
+ case ThreadDescriptorTableEntry:
+ case ThreadPerformanceCount:
+ case ThreadAmILastThread:
+ case ThreadIsIoPending:
+ case ThreadIsTerminated:
+ {
+ SpecialStatus = STATUS_INVALID_INFO_CLASS;
+ break;
+ }
+
+ /* These classes don't exist in Server 2003 SP2 */
+ case ThreadEventPair_Reusable:
+ case ThreadLastSystemCall:
+ case ThreadIoPriority:
+ case ThreadCycleTime:
+ case ThreadPagePriority:
+ case ThreadActualBasePriority:
+ case ThreadTebInformation:
+ case ThreadCSwitchMon:
+ {
+ SpecialStatus = STATUS_INVALID_INFO_CLASS;
+ break;
+ }
+
+ /* Alignment probing is not performed for this class */
+ case ThreadEnableAlignmentFaultFixup:
+ {
+ SpecialStatus = STATUS_ACCESS_VIOLATION;
+ break;
+ }
+ }
+
+ /* Set the information */
+ Status = NtSetInformationThread(NtCurrentThread(),
+ InfoClassIndex,
+ InfoPointer,
+ InfoLength);
+
+ /* And probe the results we've got */
+ ok(Status == ExpectedStatus || Status == SpecialStatus || Status == STATUS_DATATYPE_MISALIGNMENT || Status == STATUS_SUCCESS,
+ "0x%lx or special status (0x%lx) expected but got 0x%lx for class information %lu in set information thread operation!\n", ExpectedStatus, SpecialStatus, Status, InfoClassIndex);
+ }
+
+ default:
+ break;
+ }
+}
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=242efae9a22cabf961a92…
commit 242efae9a22cabf961a92785de4dde842862d388
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Mon Apr 12 14:42:52 2021 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun May 2 16:55:20 2021 +0200
[NTOS:PS] Make sure we can impersonate the given token first
PsImpersonateClient blindly impersonates the requested client even though it doesn't know if the actual token given to the call can be impersonated for the thread of the client which we are going to begin impersonation. In the case where impersonation is not possible, make a copy of the given token and assign the newly one for impersonation instead.
CORE-17539
---
ntoskrnl/ps/security.c | 53 ++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 47 insertions(+), 6 deletions(-)
diff --git a/ntoskrnl/ps/security.c b/ntoskrnl/ps/security.c
index 0b3f97fbf06..23145529743 100644
--- a/ntoskrnl/ps/security.c
+++ b/ntoskrnl/ps/security.c
@@ -614,8 +614,10 @@ PsImpersonateClient(IN PETHREAD Thread,
IN SECURITY_IMPERSONATION_LEVEL ImpersonationLevel)
{
PPS_IMPERSONATION_INFORMATION Impersonation, OldData;
- PTOKEN OldToken = NULL;
+ PTOKEN OldToken = NULL, ProcessToken = NULL;
+ PACCESS_TOKEN NewToken, ImpersonationToken;
PEJOB Job;
+ NTSTATUS Status;
PAGED_CODE();
PSTRACE(PS_SECURITY_DEBUG, "Thread: %p, Token: %p\n", Thread, Token);
@@ -670,7 +672,46 @@ PsImpersonateClient(IN PETHREAD Thread,
}
}
- /* FIXME: If the process token can't impersonate, we need to make a copy instead */
+ /*
+ * Assign the token we get from the caller first. The reason
+ * we have to do that is because we're unsure if we can impersonate
+ * in the first place. In the scenario where we cannot then the
+ * last resort is to make a copy of the token and assign that newly
+ * token to the impersonation information.
+ */
+ ImpersonationToken = Token;
+
+ /* Obtain a token from the process */
+ ProcessToken = PsReferencePrimaryToken(Thread->ThreadsProcess);
+ if (!ProcessToken)
+ {
+ /* We can't continue this way without having the process' token... */
+ return STATUS_UNSUCCESSFUL;
+ }
+
+ /* Make sure we can impersonate */
+ if (!SeTokenCanImpersonate(ProcessToken,
+ Token,
+ ImpersonationLevel))
+ {
+ /* We can't, make a copy of the token instead */
+ Status = SeCopyClientToken(Token,
+ SecurityIdentification,
+ KernelMode,
+ &NewToken);
+ if (!NT_SUCCESS(Status))
+ {
+ /* We can't even make a copy of the token? Then bail out... */
+ ObFastDereferenceObject(&Thread->ThreadsProcess->Token, ProcessToken);
+ return Status;
+ }
+
+ /* Since we cannot impersonate, assign the newly copied token */
+ ImpersonationToken = NewToken;
+ }
+
+ /* We no longer need the process' token */
+ ObFastDereferenceObject(&Thread->ThreadsProcess->Token, ProcessToken);
/* Check if this is a job */
Job = Thread->ThreadsProcess->Job;
@@ -678,14 +719,14 @@ PsImpersonateClient(IN PETHREAD Thread,
{
/* No admin allowed in this job */
if ((Job->SecurityLimitFlags & JOB_OBJECT_SECURITY_NO_ADMIN) &&
- SeTokenIsAdmin(Token))
+ SeTokenIsAdmin(ImpersonationToken))
{
return STATUS_ACCESS_DENIED;
}
/* No restricted tokens allowed in this job */
if ((Job->SecurityLimitFlags & JOB_OBJECT_SECURITY_RESTRICTED_TOKEN) &&
- SeTokenIsRestricted(Token))
+ SeTokenIsRestricted(ImpersonationToken))
{
return STATUS_ACCESS_DENIED;
}
@@ -716,8 +757,8 @@ PsImpersonateClient(IN PETHREAD Thread,
Impersonation->ImpersonationLevel = ImpersonationLevel;
Impersonation->CopyOnOpen = CopyOnOpen;
Impersonation->EffectiveOnly = EffectiveOnly;
- Impersonation->Token = Token;
- ObReferenceObject(Token);
+ Impersonation->Token = ImpersonationToken;
+ ObReferenceObject(ImpersonationToken);
/* Unlock the thread */
PspUnlockThreadSecurityExclusive(Thread);
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=18ddb6ba9248f1812b182…
commit 18ddb6ba9248f1812b182277d28d6e86707cb91f
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Mon Apr 12 14:33:42 2021 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun May 2 16:55:19 2021 +0200
[NTOS:SE] Implement SeTokenCanImpersonate routine
SeTokenCanImpersonate ensures whether the client impersonation can occur, and if not, the call signals this to the caller.
---
ntoskrnl/include/internal/se.h | 7 +++
ntoskrnl/se/token.c | 98 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 105 insertions(+)
diff --git a/ntoskrnl/include/internal/se.h b/ntoskrnl/include/internal/se.h
index 727060af822..0a8561d397a 100644
--- a/ntoskrnl/include/internal/se.h
+++ b/ntoskrnl/include/internal/se.h
@@ -248,6 +248,13 @@ SepSidInTokenEx(
IN BOOLEAN Restricted
);
+BOOLEAN
+NTAPI
+SeTokenCanImpersonate(
+ _In_ PTOKEN ProcessToken,
+ _In_ PTOKEN TokenToImpersonate,
+ _In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel);
+
/* Functions */
BOOLEAN
NTAPI
diff --git a/ntoskrnl/se/token.c b/ntoskrnl/se/token.c
index 1849a16c3a7..32842ebce99 100644
--- a/ntoskrnl/se/token.c
+++ b/ntoskrnl/se/token.c
@@ -2265,6 +2265,104 @@ SeTokenIsWriteRestricted(IN PACCESS_TOKEN Token)
return (((PTOKEN)Token)->TokenFlags & SE_BACKUP_PRIVILEGES_CHECKED) != 0;
}
+/**
+ * @brief
+ * Ensures that client impersonation can occur by checking if the token
+ * we're going to assign as the impersonation token can be actually impersonated
+ * in the first place. The routine is used primarily by PsImpersonateClient.
+ *
+ * @param[in] ProcessToken
+ * Token from a process.
+ *
+ * @param[in] TokenToImpersonate
+ * Token that we are going to impersonate.
+ *
+ * @param[in] ImpersonationLevel
+ * Security impersonation level grade.
+ *
+ * @return
+ * Returns TRUE if the conditions checked are met for token impersonation,
+ * FALSE otherwise.
+ */
+BOOLEAN
+NTAPI
+SeTokenCanImpersonate(
+ _In_ PTOKEN ProcessToken,
+ _In_ PTOKEN TokenToImpersonate,
+ _In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel)
+{
+ BOOLEAN CanImpersonate;
+ PAGED_CODE();
+
+ /*
+ * SecurityAnonymous and SecurityIdentification levels do not
+ * allow impersonation. If we get such levels from the call
+ * then something's seriously wrong.
+ */
+ ASSERT(ImpersonationLevel != SecurityAnonymous ||
+ ImpersonationLevel != SecurityIdentification);
+
+ /* Time to lock our tokens */
+ SepAcquireTokenLockShared(ProcessToken);
+ SepAcquireTokenLockShared(TokenToImpersonate);
+
+ /* What kind of authentication ID does the token have? */
+ if (RtlEqualLuid(&TokenToImpersonate->AuthenticationId,
+ &SeAnonymousAuthenticationId))
+ {
+ /*
+ * OK, it looks like the token has an anonymous
+ * authentication. Is that token created by the system?
+ */
+ if (TokenToImpersonate->TokenSource.SourceName != SeSystemTokenSource.SourceName &&
+ !RtlEqualLuid(&TokenToImpersonate->TokenSource.SourceIdentifier, &SeSystemTokenSource.SourceIdentifier))
+ {
+ /* It isn't, we can't impersonate regular tokens */
+ DPRINT("SeTokenCanImpersonate(): Token has an anonymous authentication ID, can't impersonate!\n");
+ CanImpersonate = FALSE;
+ goto Quit;
+ }
+ }
+
+ /* Are the SID values from both tokens equal? */
+ if (!RtlEqualSid(ProcessToken->UserAndGroups->Sid,
+ TokenToImpersonate->UserAndGroups->Sid))
+ {
+ /* They aren't, bail out */
+ DPRINT("SeTokenCanImpersonate(): Tokens SIDs are not equal!\n");
+ CanImpersonate = FALSE;
+ goto Quit;
+ }
+
+ /*
+ * Make sure the tokens aren't diverged in terms of
+ * restrictions, that is, one token is restricted
+ * but the other one isn't.
+ */
+ if (SeTokenIsRestricted(ProcessToken) !=
+ SeTokenIsRestricted(TokenToImpersonate))
+ {
+ /*
+ * One token is restricted so we cannot
+ * continue further at this point, bail out.
+ */
+ DPRINT("SeTokenCanImpersonate(): One token is restricted, can't continue!\n");
+ CanImpersonate = FALSE;
+ goto Quit;
+ }
+
+ /* If we've reached that far then we can impersonate! */
+ DPRINT("SeTokenCanImpersonate(): We can impersonate.\n");
+ CanImpersonate = TRUE;
+
+Quit:
+ /* We're done, unlock the tokens now */
+ SepReleaseTokenLock(ProcessToken);
+ SepReleaseTokenLock(TokenToImpersonate);
+
+ return CanImpersonate;
+}
+
/* SYSTEM CALLS ***************************************************************/
/*