mirror of
https://github.com/reactos/reactos.git
synced 2026-09-09 10:50:37 +08:00
Adds the initial attempt at handling the job ui restriction support. This doesn't protect the OS yet, however it provides everything we need to start implementing that protection.
401 lines
15 KiB
C
401 lines
15 KiB
C
/*
|
|
* PROJECT: ReactOS API Tests
|
|
* LICENSE: MIT (https://spdx.org/licenses/MIT)
|
|
* PURPOSE: Tests for the job object UI restrictions
|
|
* COPYRIGHT: Copyright 2026 Justin Miller <justin.miller@reactos.org>
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
|
|
/*
|
|
* Chromium 109, sandbox/win/src/job.cc. The JobLevel cases there fall through
|
|
* into each other, so each level takes the flags of the ones below it too:
|
|
* kLockdown adds the four here to kLimitedUser's, which comes to all eight.
|
|
*/
|
|
#define JOB_LIMITEDUSER_UI (JOB_OBJECT_UILIMIT_DISPLAYSETTINGS | \
|
|
JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS| \
|
|
JOB_OBJECT_UILIMIT_DESKTOP | \
|
|
JOB_OBJECT_UILIMIT_EXITWINDOWS)
|
|
|
|
#define JOB_LOCKDOWN_UI (JOB_LIMITEDUSER_UI | \
|
|
JOB_OBJECT_UILIMIT_WRITECLIPBOARD | \
|
|
JOB_OBJECT_UILIMIT_READCLIPBOARD | \
|
|
JOB_OBJECT_UILIMIT_HANDLES | \
|
|
JOB_OBJECT_UILIMIT_GLOBALATOMS)
|
|
|
|
static const ULONG SingleRestrictions[] =
|
|
{
|
|
JOB_OBJECT_UILIMIT_HANDLES,
|
|
JOB_OBJECT_UILIMIT_READCLIPBOARD,
|
|
JOB_OBJECT_UILIMIT_WRITECLIPBOARD,
|
|
JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS,
|
|
JOB_OBJECT_UILIMIT_DISPLAYSETTINGS,
|
|
JOB_OBJECT_UILIMIT_GLOBALATOMS,
|
|
JOB_OBJECT_UILIMIT_DESKTOP,
|
|
JOB_OBJECT_UILIMIT_EXITWINDOWS,
|
|
};
|
|
|
|
static
|
|
BOOL
|
|
SetRestrictions(
|
|
_In_ HANDLE hJob,
|
|
_In_ ULONG Restrictions)
|
|
{
|
|
JOBOBJECT_BASIC_UI_RESTRICTIONS Info;
|
|
|
|
Info.UIRestrictionsClass = Restrictions;
|
|
return SetInformationJobObject(hJob,
|
|
JobObjectBasicUIRestrictions,
|
|
&Info,
|
|
sizeof(Info));
|
|
}
|
|
|
|
static
|
|
void
|
|
test_RoundTrip(void)
|
|
{
|
|
JOBOBJECT_BASIC_UI_RESTRICTIONS Info;
|
|
HANDLE hJob;
|
|
DWORD Returned;
|
|
BOOL Success;
|
|
ULONG i;
|
|
|
|
hJob = CreateJobObjectW(NULL, NULL);
|
|
ok(hJob != NULL, "CreateJobObject failed with %lu\n", GetLastError());
|
|
if (hJob == NULL)
|
|
return;
|
|
|
|
/* A fresh job restricts nothing */
|
|
memset(&Info, 0xAA, sizeof(Info));
|
|
Returned = 0;
|
|
SetLastError(0xDEADBEEF);
|
|
Success = QueryInformationJobObject(hJob,
|
|
JobObjectBasicUIRestrictions,
|
|
&Info,
|
|
sizeof(Info),
|
|
&Returned);
|
|
ok(Success != FALSE, "QueryInformationJobObject failed with %lu\n", GetLastError());
|
|
ok_long(Info.UIRestrictionsClass, 0);
|
|
ok_long(Returned, sizeof(Info));
|
|
|
|
/* Every flag on its own, so one mistake cannot hide behind the others */
|
|
for (i = 0; i < _countof(SingleRestrictions); i++)
|
|
{
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetRestrictions(hJob, SingleRestrictions[i]);
|
|
ok(Success != FALSE, "Setting 0x%lx failed with %lu\n",
|
|
SingleRestrictions[i], GetLastError());
|
|
if (Success == FALSE)
|
|
{
|
|
/* Everything below only means something once it is set */
|
|
skip("0x%lx could not be set\n", SingleRestrictions[i]);
|
|
continue;
|
|
}
|
|
|
|
memset(&Info, 0xAA, sizeof(Info));
|
|
Returned = 0;
|
|
Success = QueryInformationJobObject(hJob,
|
|
JobObjectBasicUIRestrictions,
|
|
&Info,
|
|
sizeof(Info),
|
|
&Returned);
|
|
ok(Success != FALSE, "Querying 0x%lx failed with %lu\n",
|
|
SingleRestrictions[i], GetLastError());
|
|
if (Success == FALSE)
|
|
{
|
|
skip("0x%lx could not be queried\n", SingleRestrictions[i]);
|
|
}
|
|
else
|
|
{
|
|
ok_long(Info.UIRestrictionsClass, SingleRestrictions[i]);
|
|
ok_long(Returned, sizeof(Info));
|
|
}
|
|
|
|
/* Back to nothing, which drops the per-job state win32k keeps */
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetRestrictions(hJob, 0);
|
|
ok(Success != FALSE, "Clearing 0x%lx failed with %lu\n",
|
|
SingleRestrictions[i], GetLastError());
|
|
if (Success == FALSE)
|
|
{
|
|
/* The job stays restricted, so the round after this one would be
|
|
starting from the wrong place */
|
|
skip("0x%lx could not be cleared\n", SingleRestrictions[i]);
|
|
continue;
|
|
}
|
|
|
|
memset(&Info, 0xAA, sizeof(Info));
|
|
Success = QueryInformationJobObject(hJob,
|
|
JobObjectBasicUIRestrictions,
|
|
&Info,
|
|
sizeof(Info),
|
|
NULL);
|
|
ok(Success != FALSE, "QueryInformationJobObject failed with %lu\n", GetLastError());
|
|
if (Success != FALSE)
|
|
ok_long(Info.UIRestrictionsClass, 0);
|
|
}
|
|
|
|
/* All of them at once */
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetRestrictions(hJob, JOB_OBJECT_UILIMIT_ALL);
|
|
ok(Success != FALSE, "Setting JOB_OBJECT_UILIMIT_ALL failed with %lu\n", GetLastError());
|
|
|
|
memset(&Info, 0xAA, sizeof(Info));
|
|
Success = QueryInformationJobObject(hJob,
|
|
JobObjectBasicUIRestrictions,
|
|
&Info,
|
|
sizeof(Info),
|
|
NULL);
|
|
ok(Success != FALSE, "QueryInformationJobObject failed with %lu\n", GetLastError());
|
|
ok_long(Info.UIRestrictionsClass, JOB_OBJECT_UILIMIT_ALL);
|
|
|
|
/* Setting the same value twice is not an error */
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetRestrictions(hJob, JOB_OBJECT_UILIMIT_ALL);
|
|
ok(Success != FALSE, "Setting the same restrictions again failed with %lu\n",
|
|
GetLastError());
|
|
|
|
/* Close it while still restricted, to exercise the delete path */
|
|
CloseHandle(hJob);
|
|
}
|
|
|
|
static
|
|
void
|
|
test_InvalidParameters(void)
|
|
{
|
|
JOBOBJECT_BASIC_UI_RESTRICTIONS Info;
|
|
HANDLE hJob;
|
|
DWORD Returned;
|
|
BOOL Success;
|
|
|
|
hJob = CreateJobObjectW(NULL, NULL);
|
|
ok(hJob != NULL, "CreateJobObject failed with %lu\n", GetLastError());
|
|
if (hJob == NULL)
|
|
return;
|
|
|
|
/* Undefined bits must be rejected, and must not be stored */
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetRestrictions(hJob, 0xDEAD0000);
|
|
ok(Success == FALSE, "Setting undefined restrictions succeeded\n");
|
|
ok_err(ERROR_INVALID_PARAMETER);
|
|
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetRestrictions(hJob, JOB_OBJECT_UILIMIT_ALL | 0x100);
|
|
ok(Success == FALSE, "Setting one undefined bit succeeded\n");
|
|
ok_err(ERROR_INVALID_PARAMETER);
|
|
|
|
memset(&Info, 0xAA, sizeof(Info));
|
|
Success = QueryInformationJobObject(hJob,
|
|
JobObjectBasicUIRestrictions,
|
|
&Info,
|
|
sizeof(Info),
|
|
NULL);
|
|
ok(Success != FALSE, "QueryInformationJobObject failed with %lu\n", GetLastError());
|
|
ok_long(Info.UIRestrictionsClass, 0);
|
|
|
|
/* The class is fixed length in both directions */
|
|
Info.UIRestrictionsClass = JOB_OBJECT_UILIMIT_HANDLES;
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetInformationJobObject(hJob,
|
|
JobObjectBasicUIRestrictions,
|
|
&Info,
|
|
sizeof(Info) - 1);
|
|
ok(Success == FALSE, "SetInformationJobObject with a short buffer succeeded\n");
|
|
ok_err(ERROR_BAD_LENGTH);
|
|
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetInformationJobObject(hJob,
|
|
JobObjectBasicUIRestrictions,
|
|
&Info,
|
|
sizeof(Info) + 1);
|
|
ok(Success == FALSE, "SetInformationJobObject with a long buffer succeeded\n");
|
|
ok_err(ERROR_BAD_LENGTH);
|
|
|
|
Returned = 0;
|
|
SetLastError(0xDEADBEEF);
|
|
Success = QueryInformationJobObject(hJob,
|
|
JobObjectBasicUIRestrictions,
|
|
&Info,
|
|
sizeof(Info) - 1,
|
|
&Returned);
|
|
ok(Success == FALSE, "QueryInformationJobObject with a short buffer succeeded\n");
|
|
ok_err(ERROR_BAD_LENGTH);
|
|
|
|
/* An inaccessible buffer must be reported, not raised */
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetInformationJobObject(hJob,
|
|
JobObjectBasicUIRestrictions,
|
|
NULL,
|
|
sizeof(Info));
|
|
ok(Success == FALSE, "SetInformationJobObject with a NULL buffer succeeded\n");
|
|
ok_err(ERROR_NOACCESS);
|
|
|
|
CloseHandle(hJob);
|
|
}
|
|
|
|
/* A handle without JOB_OBJECT_SET_ATTRIBUTES may not change the restrictions */
|
|
static
|
|
void
|
|
test_Access(void)
|
|
{
|
|
JOBOBJECT_BASIC_UI_RESTRICTIONS Info;
|
|
HANDLE hJob, hQueryOnly;
|
|
BOOL Success;
|
|
|
|
hJob = CreateJobObjectW(NULL, NULL);
|
|
ok(hJob != NULL, "CreateJobObject failed with %lu\n", GetLastError());
|
|
if (hJob == NULL)
|
|
return;
|
|
|
|
hQueryOnly = NULL;
|
|
Success = DuplicateHandle(GetCurrentProcess(),
|
|
hJob,
|
|
GetCurrentProcess(),
|
|
&hQueryOnly,
|
|
JOB_OBJECT_QUERY,
|
|
FALSE,
|
|
0);
|
|
ok(Success != FALSE, "DuplicateHandle failed with %lu\n", GetLastError());
|
|
if (Success)
|
|
{
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetRestrictions(hQueryOnly, JOB_OBJECT_UILIMIT_HANDLES);
|
|
ok(Success == FALSE, "Setting restrictions through a query handle succeeded\n");
|
|
ok_err(ERROR_ACCESS_DENIED);
|
|
|
|
memset(&Info, 0xAA, sizeof(Info));
|
|
Success = QueryInformationJobObject(hQueryOnly,
|
|
JobObjectBasicUIRestrictions,
|
|
&Info,
|
|
sizeof(Info),
|
|
NULL);
|
|
ok(Success != FALSE, "QueryInformationJobObject failed with %lu\n", GetLastError());
|
|
ok_long(Info.UIRestrictionsClass, 0);
|
|
|
|
CloseHandle(hQueryOnly);
|
|
}
|
|
|
|
CloseHandle(hJob);
|
|
}
|
|
|
|
/*
|
|
* The two policies the Chromium sandbox builds, in the order it builds them.
|
|
* The GPU one excepts every UI restriction away again, which has to end up
|
|
* clearing them rather than failing.
|
|
*/
|
|
static
|
|
void
|
|
test_SandboxPolicies(void)
|
|
{
|
|
JOBOBJECT_EXTENDED_LIMIT_INFORMATION ExtendedLimit;
|
|
JOBOBJECT_BASIC_UI_RESTRICTIONS Info;
|
|
HANDLE hJob;
|
|
BOOL Success;
|
|
|
|
/* sandbox::Job::Init(JOB_LOCKDOWN) */
|
|
hJob = CreateJobObjectW(NULL, NULL);
|
|
ok(hJob != NULL, "CreateJobObject failed with %lu\n", GetLastError());
|
|
if (hJob == NULL)
|
|
return;
|
|
|
|
memset(&ExtendedLimit, 0, sizeof(ExtendedLimit));
|
|
ExtendedLimit.BasicLimitInformation.LimitFlags =
|
|
JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION |
|
|
JOB_OBJECT_LIMIT_ACTIVE_PROCESS |
|
|
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
ExtendedLimit.BasicLimitInformation.ActiveProcessLimit = 1;
|
|
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetInformationJobObject(hJob,
|
|
JobObjectExtendedLimitInformation,
|
|
&ExtendedLimit,
|
|
sizeof(ExtendedLimit));
|
|
ok(Success != FALSE, "Setting the lockdown limits failed with %lu\n", GetLastError());
|
|
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetRestrictions(hJob, JOB_LOCKDOWN_UI);
|
|
ok(Success != FALSE, "Setting the lockdown restrictions failed with %lu\n",
|
|
GetLastError());
|
|
|
|
memset(&Info, 0xAA, sizeof(Info));
|
|
Success = QueryInformationJobObject(hJob,
|
|
JobObjectBasicUIRestrictions,
|
|
&Info,
|
|
sizeof(Info),
|
|
NULL);
|
|
ok(Success != FALSE, "QueryInformationJobObject failed with %lu\n", GetLastError());
|
|
ok_long(Info.UIRestrictionsClass, JOB_LOCKDOWN_UI);
|
|
|
|
CloseHandle(hJob);
|
|
|
|
/* Job::Init(JobLevel::kLimitedUser), which does not take kLockdown's four */
|
|
hJob = CreateJobObjectW(NULL, NULL);
|
|
ok(hJob != NULL, "CreateJobObject failed with %lu\n", GetLastError());
|
|
if (hJob == NULL)
|
|
return;
|
|
|
|
memset(&ExtendedLimit, 0, sizeof(ExtendedLimit));
|
|
ExtendedLimit.BasicLimitInformation.LimitFlags =
|
|
JOB_OBJECT_LIMIT_ACTIVE_PROCESS |
|
|
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
ExtendedLimit.BasicLimitInformation.ActiveProcessLimit = 1;
|
|
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetInformationJobObject(hJob,
|
|
JobObjectExtendedLimitInformation,
|
|
&ExtendedLimit,
|
|
sizeof(ExtendedLimit));
|
|
ok(Success != FALSE, "Setting the limited user limits failed with %lu\n", GetLastError());
|
|
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetRestrictions(hJob, JOB_LIMITEDUSER_UI);
|
|
ok(Success != FALSE, "Setting the limited user restrictions failed with %lu\n",
|
|
GetLastError());
|
|
|
|
memset(&Info, 0xAA, sizeof(Info));
|
|
Success = QueryInformationJobObject(hJob,
|
|
JobObjectBasicUIRestrictions,
|
|
&Info,
|
|
sizeof(Info),
|
|
NULL);
|
|
ok(Success != FALSE, "QueryInformationJobObject failed with %lu\n", GetLastError());
|
|
ok_long(Info.UIRestrictionsClass, JOB_LIMITEDUSER_UI);
|
|
|
|
CloseHandle(hJob);
|
|
|
|
/*
|
|
* job.cc applies the caller's exceptions as
|
|
* jbur.UIRestrictionsClass &= ~ui_exceptions before its single
|
|
* SetInformationJobObject, so a delegate that excepts everything away asks
|
|
* for a mask of zero. That has to be accepted, not refused.
|
|
*/
|
|
hJob = CreateJobObjectW(NULL, NULL);
|
|
ok(hJob != NULL, "CreateJobObject failed with %lu\n", GetLastError());
|
|
if (hJob == NULL)
|
|
return;
|
|
|
|
SetLastError(0xDEADBEEF);
|
|
Success = SetRestrictions(hJob, JOB_LOCKDOWN_UI & ~JOB_OBJECT_UILIMIT_ALL);
|
|
ok(Success != FALSE, "Excepting every restriction away failed with %lu\n",
|
|
GetLastError());
|
|
|
|
memset(&Info, 0xAA, sizeof(Info));
|
|
Success = QueryInformationJobObject(hJob,
|
|
JobObjectBasicUIRestrictions,
|
|
&Info,
|
|
sizeof(Info),
|
|
NULL);
|
|
ok(Success != FALSE, "QueryInformationJobObject failed with %lu\n", GetLastError());
|
|
ok_long(Info.UIRestrictionsClass, 0);
|
|
|
|
CloseHandle(hJob);
|
|
}
|
|
|
|
START_TEST(JobObject)
|
|
{
|
|
test_RoundTrip();
|
|
test_InvalidParameters();
|
|
test_Access();
|
|
test_SandboxPolicies();
|
|
}
|