1 /**
2  * FreeRDP: A Remote Desktop Protocol Implementation
3  * TestSyncAPC
4  *
5  * Copyright 2021 David Fort <contact@hardening-consulting.com>
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 #include <winpr/wtypes.h>
20 #include <winpr/thread.h>
21 #include <winpr/synch.h>
22 
23 typedef struct
24 {
25 	BOOL error;
26 	BOOL called;
27 } UserApcArg;
28 
userApc(ULONG_PTR arg)29 void CALLBACK userApc(ULONG_PTR arg)
30 {
31 	UserApcArg* userArg = (UserApcArg*)arg;
32 	userArg->called = TRUE;
33 }
34 
uncleanThread(LPVOID lpThreadParameter)35 static DWORD WINAPI uncleanThread(LPVOID lpThreadParameter)
36 {
37 	/* this thread post an APC that will never get executed */
38 	UserApcArg* userArg = (UserApcArg*)lpThreadParameter;
39 	if (!QueueUserAPC((PAPCFUNC)userApc, _GetCurrentThread(), (ULONG_PTR)lpThreadParameter))
40 	{
41 		userArg->error = TRUE;
42 		return 1;
43 	}
44 
45 	return 0;
46 }
47 
cleanThread(LPVOID lpThreadParameter)48 static DWORD WINAPI cleanThread(LPVOID lpThreadParameter)
49 {
50 	Sleep(500);
51 
52 	SleepEx(500, TRUE);
53 	return 0;
54 }
55 
56 typedef struct
57 {
58 	HANDLE timer1;
59 	DWORD timer1Calls;
60 	HANDLE timer2;
61 	DWORD timer2Calls;
62 	BOOL endTest;
63 } UncleanCloseData;
64 
Timer1APCProc(LPVOID lpArg,DWORD dwTimerLowValue,DWORD dwTimerHighValue)65 static VOID CALLBACK Timer1APCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
66 {
67 	UncleanCloseData* data = (UncleanCloseData*)lpArg;
68 	data->timer1Calls++;
69 	CloseHandle(data->timer2);
70 	data->endTest = TRUE;
71 }
72 
Timer2APCProc(LPVOID lpArg,DWORD dwTimerLowValue,DWORD dwTimerHighValue)73 static VOID CALLBACK Timer2APCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
74 {
75 	UncleanCloseData* data = (UncleanCloseData*)lpArg;
76 	data->timer2Calls++;
77 }
78 
closeHandleTest(LPVOID lpThreadParameter)79 static DWORD /*WINAPI*/ closeHandleTest(LPVOID lpThreadParameter)
80 {
81 	LARGE_INTEGER dueTime;
82 	UncleanCloseData* data = (UncleanCloseData*)lpThreadParameter;
83 	data->endTest = FALSE;
84 
85 	dueTime.QuadPart = -500;
86 	if (!SetWaitableTimer(data->timer1, &dueTime, 0, Timer1APCProc, lpThreadParameter, FALSE))
87 		return 1;
88 
89 	dueTime.QuadPart = -900;
90 	if (!SetWaitableTimer(data->timer2, &dueTime, 0, Timer2APCProc, lpThreadParameter, FALSE))
91 		return 1;
92 
93 	while (!data->endTest)
94 	{
95 		SleepEx(100, TRUE);
96 	}
97 	return 0;
98 }
99 
TestSynchAPC(int argc,char * argv[])100 int TestSynchAPC(int argc, char* argv[])
101 {
102 	HANDLE thread = NULL;
103 	UserApcArg userApcArg;
104 	UncleanCloseData uncleanCloseData;
105 
106 	userApcArg.error = FALSE;
107 	userApcArg.called = FALSE;
108 
109 	WINPR_UNUSED(argc);
110 	WINPR_UNUSED(argv);
111 
112 	/* first post an APC and check it is executed during a SleepEx */
113 	if (!QueueUserAPC((PAPCFUNC)userApc, _GetCurrentThread(), (ULONG_PTR)&userApcArg))
114 		return 1;
115 
116 	if (SleepEx(100, FALSE) != 0)
117 		return 2;
118 
119 	if (SleepEx(100, TRUE) != WAIT_IO_COMPLETION)
120 		return 3;
121 
122 	if (!userApcArg.called)
123 		return 4;
124 
125 	userApcArg.called = FALSE;
126 
127 	/* test that the APC is cleaned up even when not called */
128 	thread = CreateThread(NULL, 0, uncleanThread, &userApcArg, 0, NULL);
129 	if (!thread)
130 		return 10;
131 	WaitForSingleObject(thread, INFINITE);
132 	CloseHandle(thread);
133 
134 	if (userApcArg.called || userApcArg.error)
135 		return 11;
136 
137 	/* test a remote APC queuing */
138 	thread = CreateThread(NULL, 0, cleanThread, &userApcArg, 0, NULL);
139 	if (!thread)
140 		return 20;
141 
142 	if (!QueueUserAPC((PAPCFUNC)userApc, thread, (ULONG_PTR)&userApcArg))
143 		return 21;
144 
145 	WaitForSingleObject(thread, INFINITE);
146 	CloseHandle(thread);
147 
148 	if (!userApcArg.called)
149 		return 22;
150 
151 #if 0
152 	/* test cleanup of timer completions */
153 	memset(&uncleanCloseData, 0, sizeof(uncleanCloseData));
154 	uncleanCloseData.timer1 = CreateWaitableTimerA(NULL, FALSE, NULL);
155 	if (!uncleanCloseData.timer1)
156 		return 31;
157 
158 	uncleanCloseData.timer2 = CreateWaitableTimerA(NULL, FALSE, NULL);
159 	if (!uncleanCloseData.timer2)
160 		return 32;
161 
162 	thread = CreateThread(NULL, 0, closeHandleTest, &uncleanCloseData, 0, NULL);
163 	if (!thread)
164 		return 33;
165 
166 	WaitForSingleObject(thread, INFINITE);
167 	CloseHandle(thread);
168 
169 	if (uncleanCloseData.timer1Calls != 1 || uncleanCloseData.timer2Calls != 0)
170 		return 34;
171 	CloseHandle(uncleanCloseData.timer1);
172 #endif
173 	return 0;
174 }
175