ReactOS 0.4.17-dev-736-g75e91de
UserHandleGrantAccess.c
Go to the documentation of this file.
1/*
2 * PROJECT: ReactOS API Tests
3 * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
4 * PURPOSE: Tests for UserHandleGrantAccess and the job UI restriction callouts
5 * COPYRIGHT: Copyright 2026 Justin Miller <justin.miller@reactos.org>
6 */
7
8#include "precomp.h"
9
10#include <strsafe.h>
11
12#define READY_EVENT L"user32_apitest_UserHandleGrantAccess_ready"
13#define QUIT_EVENT L"user32_apitest_UserHandleGrantAccess_quit"
14
15#define GrantAccess(handle, job) UserHandleGrantAccess((handle), (job), TRUE)
16#define RevokeAccess(handle, job) UserHandleGrantAccess((handle), (job), FALSE)
17
18/*
19 * Chromium 109, sandbox/win/src/job.cc. The JobLevel cases there fall through
20 * into each other, so kLockdown takes its own four flags plus those of
21 * kLimitedUser and kInteractive below it, which comes to all eight.
22 */
23#define JOB_LOCKDOWN_UI (JOB_OBJECT_UILIMIT_HANDLES | \
24 JOB_OBJECT_UILIMIT_READCLIPBOARD | \
25 JOB_OBJECT_UILIMIT_WRITECLIPBOARD | \
26 JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS| \
27 JOB_OBJECT_UILIMIT_DISPLAYSETTINGS | \
28 JOB_OBJECT_UILIMIT_GLOBALATOMS | \
29 JOB_OBJECT_UILIMIT_DESKTOP | \
30 JOB_OBJECT_UILIMIT_EXITWINDOWS)
31
32static
35{
37 HANDLE hJob;
38
39 hJob = CreateJobObjectW(NULL, NULL);
40 if (hJob == NULL)
41 {
42 skip("CreateJobObject failed with %lu\n", GetLastError());
43 return NULL;
44 }
45
46 if (Restrictions != 0)
47 {
48 Info.UIRestrictionsClass = Restrictions;
51 &Info,
52 sizeof(Info)))
53 {
54 skip("Setting restrictions 0x%lx failed with %lu\n",
55 Restrictions, GetLastError());
56 CloseHandle(hJob);
57 return NULL;
58 }
59 }
60
61 return hJob;
62}
63
64static
65void
67{
68 HANDLE hJob;
69 HWND hWnd;
71
73 L"Static",
74 L"UserHandleGrantAccess",
76 0, 0, 10, 10,
77 NULL, NULL, NULL, NULL);
78 ok(hWnd != NULL, "CreateWindowEx failed with %lu\n", GetLastError());
79 if (hWnd == NULL)
80 {
81 skip("No window to grant access to\n");
82 return;
83 }
84
85 /* A job that restricts nothing at all keeps no granted list */
86 hJob = CreateRestrictedJob(0);
87 if (hJob != NULL)
88 {
89 SetLastError(0xDEADBEEF);
90 Success = GrantAccess(hWnd, hJob);
91 ok(Success == FALSE, "Granting to an unrestricted job succeeded\n");
93 CloseHandle(hJob);
94 }
95
96 /* Any restriction is enough to get one, though. It is having a list at
97 all that decides, not whether the job restricts handles in particular. */
99 if (hJob != NULL)
100 {
101 SetLastError(0xDEADBEEF);
102 Success = GrantAccess(hWnd, hJob);
103 ok(Success != FALSE, "Granting to a restricted job failed with %lu\n",
104 GetLastError());
105 CloseHandle(hJob);
106 }
107
109 if (hJob != NULL)
110 {
111 SetLastError(0xDEADBEEF);
112 Success = GrantAccess(hWnd, hJob);
113 ok(Success != FALSE, "Granting failed with %lu\n", GetLastError());
114
115 /* granting one twice adds a single entry, and is intended to be proven by this test */
116 SetLastError(0xDEADBEEF);
117 Success = GrantAccess(hWnd, hJob);
118 ok(Success != FALSE, "Granting twice failed with %lu\n", GetLastError());
119
120 /* So one revoke is all it takes to put the handle out of reach again */
121 SetLastError(0xDEADBEEF);
122 Success = RevokeAccess(hWnd, hJob);
123 ok(Success != FALSE, "Revoking failed with %lu\n", GetLastError());
124
125 /* And revoking what is no longer on the list is not an error either */
126 SetLastError(0xDEADBEEF);
127 Success = RevokeAccess(hWnd, hJob);
128 ok(Success != FALSE, "Revoking twice failed with %lu\n", GetLastError());
129
130 /* Only a handle that exists can be named, either way round */
131 SetLastError(0xDEADBEEF);
132 Success = GrantAccess((HANDLE)(ULONG_PTR)0x0000BEEF, hJob);
133 ok(Success == FALSE, "Granting a handle that does not exist succeeded\n");
135
136 {
137 HWND hWndGone;
138
139 hWndGone = CreateWindowExW(0, L"Static", NULL, WS_POPUP,
140 0, 0, 10, 10,
141 NULL, NULL, NULL, NULL);
142 if (hWndGone != NULL)
143 {
144 DestroyWindow(hWndGone);
145
146 SetLastError(0xDEADBEEF);
147 Success = GrantAccess(hWndGone, hJob);
148 ok(Success == FALSE, "Granting a destroyed window succeeded\n");
150
151 /* Revoking is refused just the same. A handle destroyed while
152 granted is taken off the list when it is freed, so there is
153 never a dead one left for the caller to revoke by hand. */
154 SetLastError(0xDEADBEEF);
155 Success = RevokeAccess(hWndGone, hJob);
156 ok(Success == FALSE, "Revoking a destroyed window succeeded\n");
158 }
159 }
160
161 /* Enough handles to make the granted list grow more than once */
162 {
163 HWND Windows[16];
164 ULONG i;
165
166 for (i = 0; i < _countof(Windows); i++)
167 {
168 Windows[i] = CreateWindowExW(0, L"Static", NULL, WS_POPUP,
169 0, 0, 10, 10,
170 NULL, NULL, NULL, NULL);
171 if (Windows[i] == NULL)
172 {
173 skip("CreateWindowEx failed with %lu\n", GetLastError());
174 break;
175 }
176
177 Success = GrantAccess(Windows[i], hJob);
178 ok(Success != FALSE, "Granting window %lu failed with %lu\n",
179 i, GetLastError());
180 }
181
182 while (i-- > 0)
183 {
184 Success = RevokeAccess(Windows[i], hJob);
185 ok(Success != FALSE, "Revoking window %lu failed with %lu\n",
186 i, GetLastError());
187 DestroyWindow(Windows[i]);
188 }
189 }
190
191 /* Destroying a granted window has to withdraw the grant. The list
192 cannot be read from here, so this only shows it stays intact. */
193 {
194 HWND Windows[8];
195 ULONG i;
196
197 for (i = 0; i < _countof(Windows); i++)
198 {
199 Windows[i] = CreateWindowExW(0, L"Static", NULL, WS_POPUP,
200 0, 0, 10, 10,
201 NULL, NULL, NULL, NULL);
202 if (Windows[i] == NULL)
203 {
204 skip("CreateWindowEx failed with %lu\n", GetLastError());
205 break;
206 }
207
208 Success = GrantAccess(Windows[i], hJob);
209 ok(Success != FALSE, "Granting window %lu failed with %lu\n",
210 i, GetLastError());
211 }
212
213 /* Destroy them without revoking first */
214 while (i-- > 0)
215 DestroyWindow(Windows[i]);
216
217 /* The list has to still work afterwards */
218 SetLastError(0xDEADBEEF);
219 Success = GrantAccess(hWnd, hJob);
220 ok(Success != FALSE, "Granting after a sweep failed with %lu\n",
221 GetLastError());
222 SetLastError(0xDEADBEEF);
223 Success = RevokeAccess(hWnd, hJob);
224 ok(Success != FALSE, "Revoking after a sweep failed with %lu\n",
225 GetLastError());
226 }
227
228 /* Close the job with handles still granted, to free the list */
229 Success = GrantAccess(hWnd, hJob);
230 ok(Success != FALSE, "Granting failed with %lu\n", GetLastError());
231 CloseHandle(hJob);
232 }
233
234 /* A handle that is not a job at all */
235 SetLastError(0xDEADBEEF);
237 ok(Success == FALSE, "Granting against a process handle succeeded\n");
238
239 SetLastError(0xDEADBEEF);
241 ok(Success == FALSE, "Granting against a NULL job succeeded\n");
242
244}
245
246static
247HANDLE
249{
251 WCHAR CommandLine[MAX_PATH];
252 STARTUPINFOW StartupInfo;
253 PROCESS_INFORMATION ProcessInfo;
254
256 StringCbPrintfW(CommandLine,
257 sizeof(CommandLine),
258 L"\"%ls\" UserHandleGrantAccess child",
259 FileName);
260
261 ZeroMemory(&StartupInfo, sizeof(StartupInfo));
262 StartupInfo.cb = sizeof(StartupInfo);
263 StartupInfo.dwFlags = STARTF_USESTDHANDLES;
264
266 CommandLine,
267 NULL,
268 NULL,
269 FALSE,
270 0,
271 NULL,
272 NULL,
273 &StartupInfo,
274 &ProcessInfo))
275 {
276 skip("CreateProcess failed with %lu\n", GetLastError());
277 *Thread = NULL;
278 return NULL;
279 }
280
281 *Thread = ProcessInfo.hThread;
282 return ProcessInfo.hProcess;
283}
284
285/*
286 * Runs a child through a job. RestrictFirst TRUE assigns into an already
287 * restricted job, so the kernel hands the process over from the assignment
288 * path; FALSE restricts afterwards, so win32k has to find it itself.
289 */
290static
291void
293 _In_ BOOL RestrictFirst,
294 _In_ HANDLE hReady,
295 _In_ HANDLE hQuit)
296{
298 HANDLE hJob, hProcess, hThread;
299 DWORD Wait;
301
302 ResetEvent(hReady);
303 ResetEvent(hQuit);
304
305 hJob = CreateRestrictedJob(RestrictFirst ? JOB_LOCKDOWN_UI : 0);
306 if (hJob == NULL)
307 return;
308
310 if (hProcess == NULL)
311 {
312 CloseHandle(hJob);
313 return;
314 }
315
316 /* The callout is only made for a process that is a win32k client */
317 Wait = WaitForSingleObject(hReady, 10000);
318 ok(Wait == WAIT_OBJECT_0, "The child did not become ready, wait returned %lu\n", Wait);
319
320 if (Wait == WAIT_OBJECT_0)
321 {
322 SetLastError(0xDEADBEEF);
325 {
326 /* The test itself is running in a job that does not allow
327 breakaway, so the child inherited it */
328 skip("The test is already running in a job\n");
329 }
330 else
331 {
332 ok(Success != FALSE, "AssignProcessToJobObject failed with %lu\n",
333 GetLastError());
334
335 /* Restricted up front, the child leaves the job by exiting.
336 Otherwise restrict it now and lift it again, so win32k has to
337 let go of a process it still holds. */
338 if (!RestrictFirst)
339 {
340 Info.UIRestrictionsClass = JOB_LOCKDOWN_UI;
341 SetLastError(0xDEADBEEF);
344 &Info,
345 sizeof(Info));
346 ok(Success != FALSE, "Restricting a populated job failed with %lu\n",
347 GetLastError());
348
349 Info.UIRestrictionsClass = 0;
350 SetLastError(0xDEADBEEF);
353 &Info,
354 sizeof(Info));
355 ok(Success != FALSE, "Clearing the restrictions failed with %lu\n",
356 GetLastError());
357 }
358 }
359 }
360
361 SetEvent(hQuit);
363 ok(Wait == WAIT_OBJECT_0, "The child did not exit, wait returned %lu\n", Wait);
364 if (Wait != WAIT_OBJECT_0)
366
369 CloseHandle(hJob);
370}
371
372static
373void
375{
376 HANDLE hReady, hQuit;
377
380
381 if (hReady != NULL && hQuit != NULL)
382 RunProcessInJob(RestrictFirst, hReady, hQuit);
383 else
384 skip("CreateEvent failed with %lu\n", GetLastError());
385
386 if (hReady != NULL)
387 CloseHandle(hReady);
388 if (hQuit != NULL)
389 CloseHandle(hQuit);
390}
391
392/* The child: become a win32k client, say so, and wait to be let go */
393static
394void
396{
397 HANDLE hReady, hQuit;
398
399 /* Any USER call connects us to win32k */
401
404
405 if (hReady != NULL)
406 {
407 SetEvent(hReady);
408 CloseHandle(hReady);
409 }
410
411 if (hQuit != NULL)
412 {
413 WaitForSingleObject(hQuit, 30000);
414 CloseHandle(hQuit);
415 }
416}
417
419{
420 char **argv;
421 int argc;
422
424 if (argc >= 3 && !strcmp(argv[2], "child"))
425 {
426 RunChild();
427 return;
428 }
429
433}
static HANDLE CreateRestrictedJob(_In_ ULONG Restrictions)
#define READY_EVENT
static HANDLE StartChild(_Out_ PHANDLE Thread)
#define GrantAccess(handle, job)
#define RevokeAccess(handle, job)
#define QUIT_EVENT
static void test_ProcessInJob(_In_ BOOL RestrictFirst)
static void RunChild(void)
static void test_GrantAccess(void)
#define JOB_LOCKDOWN_UI
static void RunProcessInJob(_In_ BOOL RestrictFirst, _In_ HANDLE hReady, _In_ HANDLE hQuit)
#define ok(value,...)
Definition: atltest.h:57
#define skip(...)
Definition: atltest.h:64
#define ok_err(error)
Definition: atltest.h:124
#define START_TEST(x)
Definition: atltest.h:75
HWND hWnd
Definition: settings.c:17
#define NULL
Definition: types.h:112
#define TRUE
Definition: types.h:120
#define FALSE
Definition: types.h:117
#define CloseHandle
Definition: compat.h:739
#define ERROR_INVALID_PARAMETER
Definition: compat.h:101
#define SetLastError(x)
Definition: compat.h:752
#define GetCurrentProcess()
Definition: compat.h:759
#define MAX_PATH
Definition: compat.h:34
#define ERROR_ACCESS_DENIED
Definition: compat.h:97
BOOL WINAPI SetInformationJobObject(_In_ HANDLE hJob, _In_ JOBOBJECTINFOCLASS JobObjectInformationClass, _In_reads_bytes_(cbJobObjectInformationLength) LPVOID lpJobObjectInformation, _In_ DWORD cbJobObjectInformationLength)
Definition: job.c:220
HANDLE WINAPI CreateJobObjectW(_In_ LPSECURITY_ATTRIBUTES lpJobAttributes, _In_ LPCWSTR lpName)
Definition: job.c:39
BOOL WINAPI AssignProcessToJobObject(_In_ HANDLE hJob, _In_ HANDLE hProcess)
Definition: job.c:104
DWORD WINAPI GetModuleFileNameW(HINSTANCE hModule, LPWSTR lpFilename, DWORD nSize)
Definition: loader.c:600
BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessW(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
Definition: proc.c:4491
BOOL WINAPI TerminateProcess(IN HANDLE hProcess, IN UINT uExitCode)
Definition: proc.c:1425
MonoAssembly int argc
Definition: metahost.c:107
_ACRTIMP int __cdecl strcmp(const char *, const char *)
Definition: string.c:3324
#define L(x)
Definition: resources.c:13
@ Success
Definition: eventcreate.c:712
unsigned int BOOL
Definition: ntddk_ex.h:94
unsigned long DWORD
Definition: ntddk_ex.h:95
_In_opt_ PFILE_OBJECT _In_opt_ PETHREAD Thread
Definition: fltkernel.h:2653
GLsizei GLenum const GLvoid GLsizei GLenum GLbyte GLbyte GLbyte GLdouble GLdouble GLdouble GLfloat GLfloat GLfloat GLint GLint GLint GLshort GLshort GLshort GLubyte GLubyte GLubyte GLuint GLuint GLuint GLushort GLushort GLushort GLbyte GLbyte GLbyte GLbyte GLdouble GLdouble GLdouble GLdouble GLfloat GLfloat GLfloat GLfloat GLint GLint GLint GLint GLshort GLshort GLshort GLshort GLubyte GLubyte GLubyte GLubyte GLuint GLuint GLuint GLuint GLushort GLushort GLushort GLushort GLboolean const GLdouble const GLfloat const GLint const GLshort const GLbyte const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLdouble const GLfloat const GLfloat const GLint const GLint const GLshort const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort GLenum GLenum GLenum GLfloat GLenum GLint GLenum GLenum GLenum GLfloat GLenum GLenum GLint GLenum GLfloat GLenum GLint GLint GLushort GLenum GLenum GLfloat GLenum GLenum GLint GLfloat const GLubyte GLenum GLenum GLenum const GLfloat GLenum GLenum const GLint GLenum GLint GLint GLsizei GLsizei GLint GLenum GLenum const GLvoid GLenum GLenum const GLfloat GLenum GLenum const GLint GLenum GLenum const GLdouble GLenum GLenum const GLfloat GLenum GLenum const GLint GLsizei GLuint GLfloat GLuint GLbitfield GLfloat GLint GLuint GLboolean GLenum GLfloat GLenum GLbitfield GLenum GLfloat GLfloat GLint GLint const GLfloat GLenum GLfloat GLfloat GLint GLint GLfloat GLfloat GLint GLint const GLfloat GLint GLfloat GLfloat GLint GLfloat GLfloat GLint GLfloat GLfloat const GLdouble const GLfloat const GLdouble const GLfloat GLint i
Definition: glfuncs.h:248
@ JobObjectBasicUIRestrictions
Definition: pstypes.h:495
#define JOB_OBJECT_UILIMIT_HANDLES
Definition: pstypes.h:227
#define JOB_OBJECT_UILIMIT_EXITWINDOWS
Definition: pstypes.h:234
_In_ BOOL _In_ HANDLE hProcess
Definition: mapping.h:71
#define ZeroMemory
Definition: minwinbase.h:31
#define argv
Definition: mplay32.c:18
HANDLE hThread
Definition: wizard.c:28
#define _Out_
Definition: no_sal2.h:160
#define _In_
Definition: no_sal2.h:158
#define SYNCHRONIZE
Definition: nt_native.h:61
PVOID *typedef PHANDLE
Definition: ntsecpkg.h:455
short WCHAR
Definition: pedump.c:58
#define WS_POPUP
Definition: pedump.c:616
int winetest_get_mainargs(char ***pargv)
#define _countof(array)
Definition: sndvol32.h:70
STRSAFEAPI StringCbPrintfW(STRSAFE_LPWSTR pszDest, size_t cbDest, STRSAFE_LPCWSTR pszFormat,...)
Definition: strsafe.h:557
HANDLE WINAPI DECLSPEC_HOTPATCH OpenEventW(IN DWORD dwDesiredAccess, IN BOOL bInheritHandle, IN LPCWSTR lpName)
Definition: synch.c:618
DWORD WINAPI WaitForSingleObject(IN HANDLE hHandle, IN DWORD dwMilliseconds)
Definition: synch.c:82
HANDLE WINAPI DECLSPEC_HOTPATCH CreateEventW(IN LPSECURITY_ATTRIBUTES lpEventAttributes OPTIONAL, IN BOOL bManualReset, IN BOOL bInitialState, IN LPCWSTR lpName OPTIONAL)
Definition: synch.c:587
BOOL WINAPI DECLSPEC_HOTPATCH SetEvent(IN HANDLE hEvent)
Definition: synch.c:669
BOOL WINAPI DECLSPEC_HOTPATCH ResetEvent(IN HANDLE hEvent)
Definition: synch.c:650
uint32_t ULONG_PTR
Definition: typedefs.h:65
uint32_t ULONG
Definition: typedefs.h:59
_Must_inspect_result_ _In_ WDFCHILDLIST _In_ PWDF_CHILD_LIST_ITERATOR _Out_ WDFDEVICE _Inout_opt_ PWDF_CHILD_RETRIEVE_INFO Info
Definition: wdfchildlist.h:690
_In_ WDFDPC _In_ BOOLEAN Wait
Definition: wdfdpc.h:170
BOOL WINAPI UserHandleGrantAccess(HANDLE handle, HANDLE job, BOOL grant)
Definition: misc.c:495
DWORD WINAPI GetLastError(void)
Definition: except.c:1042
#define EVENT_MODIFY_STATE
Definition: winbase.h:166
#define WAIT_OBJECT_0
Definition: winbase.h:383
#define STARTF_USESTDHANDLES
Definition: winbase.h:476
HWND WINAPI GetDesktopWindow(void)
Definition: window.c:628
HWND WINAPI CreateWindowExW(_In_ DWORD dwExStyle, _In_opt_ LPCWSTR lpClassName, _In_opt_ LPCWSTR lpWindowName, _In_ DWORD dwStyle, _In_ int X, _In_ int Y, _In_ int nWidth, _In_ int nHeight, _In_opt_ HWND hWndParent, _In_opt_ HMENU hMenu, _In_opt_ HINSTANCE hInstance, _In_opt_ LPVOID lpParam)
BOOL WINAPI DestroyWindow(_In_ HWND)