The open source OpenXR runtime
1// Copyright 2022, Magic Leap, Inc.
2// Copyright 2020-2022, Collabora, Ltd.
3// SPDX-License-Identifier: BSL-1.0
4/*!
5 * @file
6 * @brief Server mainloop details on Windows.
7 * @author Julian Petrov <jpetrov@magicleap.com>
8 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
9 * @author Jakob Bornecrantz <jakob@collabora.com>
10 * @ingroup ipc_server
11 */
12
13#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
14#define _CRT_SECURE_NO_WARNINGS
15#endif
16
17#include "xrt/xrt_device.h"
18#include "xrt/xrt_instance.h"
19#include "xrt/xrt_compositor.h"
20#include "xrt/xrt_config_have.h"
21#include "xrt/xrt_config_os.h"
22
23#include "os/os_time.h"
24#include "util/u_var.h"
25#include "util/u_misc.h"
26#include "util/u_debug.h"
27#include "util/u_trace_marker.h"
28#include "util/u_file.h"
29#include "util/u_windows.h"
30
31#include "shared/ipc_utils.h"
32#include "shared/ipc_shmem.h"
33#include "server/ipc_server.h"
34
35#include <conio.h>
36#include <sddl.h>
37
38
39/*
40 *
41 * Helpers.
42 *
43 */
44
45#define ERROR_STR(BUF, ERR) (u_winerror(BUF, ARRAY_SIZE(BUF), ERR, true))
46
47DEBUG_GET_ONCE_BOOL_OPTION(relaxed, "IPC_RELAXED_CONNECTION_SECURITY", false)
48
49
50/*
51 *
52 * Static functions.
53 *
54 */
55
56template <unsigned int N>
57static char *
58get_current_process_name(char (&path)[N])
59{
60 GetModuleFileNameA(NULL, path, N);
61 char *exe = strrchr(path, '\\');
62 if (exe) {
63 return exe + 1;
64 }
65 return path;
66}
67
68ULONG
69get_pipe_server_pid(const char *pipe_name)
70{
71 ULONG pid = 0;
72 HANDLE h = CreateNamedPipeA( //
73 pipe_name, //
74 PIPE_ACCESS_DUPLEX, //
75 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_NOWAIT | PIPE_REJECT_REMOTE_CLIENTS, //
76 IPC_MAX_CLIENTS, //
77 IPC_BUF_SIZE, //
78 IPC_BUF_SIZE, //
79 0, //
80 nullptr);
81 if (h != INVALID_HANDLE_VALUE) {
82 GetNamedPipeServerProcessId(h, &pid);
83 CloseHandle(h);
84 }
85 return pid;
86}
87
88static bool
89create_pipe_instance(struct ipc_server_mainloop *ml, bool first)
90{
91 SECURITY_ATTRIBUTES sa{};
92 sa.nLength = sizeof(sa);
93 sa.lpSecurityDescriptor = nullptr;
94 sa.bInheritHandle = FALSE;
95
96 /*
97 * Change the pipe's DACL to allow other users access.
98 *
99 * https://learn.microsoft.com/en-us/windows/win32/secbp/creating-a-dacl
100 * https://learn.microsoft.com/en-us/windows/win32/secauthz/sid-strings
101 */
102 const TCHAR *str = //
103 TEXT("D:") // Discretionary ACL
104 TEXT("(D;OICI;GA;;;BG)") // Guest: deny
105 TEXT("(D;OICI;GA;;;AN)") // Anonymous: deny
106 TEXT("(A;OICI;GRGWGX;;;AC)") // UWP/AppContainer packages: read/write/execute
107 TEXT("(A;OICI;GRGWGX;;;AU)") // Authenticated user: read/write/execute
108 TEXT("(A;OICI;GA;;;BA)"); // Administrator: full control
109
110 BOOL bret = ConvertStringSecurityDescriptorToSecurityDescriptor( //
111 str, // StringSecurityDescriptor
112 SDDL_REVISION_1, // StringSDRevision
113 &sa.lpSecurityDescriptor, // SecurityDescriptor
114 NULL); // SecurityDescriptorSize
115 if (!bret) {
116 DWORD err = GetLastError();
117 char buffer[1024];
118 U_LOG_E("ConvertStringSecurityDescriptorToSecurityDescriptor: %u %s", err, ERROR_STR(buffer, err));
119 }
120
121 LPSECURITY_ATTRIBUTES lpsa = nullptr;
122 if (debug_get_bool_option_relaxed()) {
123 U_LOG_W("Using relax security permissions on pipe");
124 lpsa = &sa;
125 }
126
127 DWORD dwOpenMode = PIPE_ACCESS_DUPLEX;
128 DWORD dwPipeMode = PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_NOWAIT | PIPE_REJECT_REMOTE_CLIENTS;
129
130 if (first) {
131 dwOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE;
132 }
133
134 ml->pipe_handle = CreateNamedPipeA( //
135 ml->pipe_name, //
136 dwOpenMode, //
137 dwPipeMode, //
138 IPC_MAX_CLIENTS, //
139 IPC_BUF_SIZE, //
140 IPC_BUF_SIZE, //
141 0, //
142 lpsa); //
143
144 if (sa.lpSecurityDescriptor != nullptr) {
145 // Need to free the security descriptor.
146 LocalFree(sa.lpSecurityDescriptor);
147 sa.lpSecurityDescriptor = nullptr;
148 }
149
150 if (ml->pipe_handle != INVALID_HANDLE_VALUE) {
151 return true;
152 }
153
154 DWORD err = GetLastError();
155 if (err == ERROR_PIPE_BUSY) {
156 U_LOG_W("CreateNamedPipeA failed: %d %s An existing client must disconnect first!", err,
157 ipc_winerror(err));
158 } else {
159 U_LOG_E("CreateNamedPipeA failed: %d %s", err, ipc_winerror(err));
160 if (err == ERROR_ACCESS_DENIED && first) {
161 char path[MAX_PATH];
162 char *exe = get_current_process_name(path);
163 ULONG pid = get_pipe_server_pid(ml->pipe_name);
164 if (pid) {
165 U_LOG_E(
166 "An existing process id %d has the communication pipe already created. You likely "
167 "have \"%s\" running already. This service instance cannot continue...",
168 pid, exe);
169 } else {
170 U_LOG_E(
171 "You likely have \"%s\" running already. This service instance cannot continue...",
172 exe);
173 }
174 }
175 }
176
177 return false;
178}
179
180static void
181create_another_pipe_instance(struct ipc_server *vs, struct ipc_server_mainloop *ml)
182{
183 if (!create_pipe_instance(ml, false)) {
184 ipc_server_handle_failure(vs);
185 }
186}
187
188static void
189handle_connected_client(struct ipc_server *vs, struct ipc_server_mainloop *ml)
190{
191 DWORD mode = PIPE_READMODE_MESSAGE | PIPE_WAIT;
192 BOOL bRet;
193
194 bRet = SetNamedPipeHandleState(ml->pipe_handle, &mode, nullptr, nullptr);
195 if (bRet) {
196 // Call into the generic client connected handling code.
197 ipc_server_handle_client_connected(vs, ml->pipe_handle);
198
199 // Create another pipe to wait on.
200 create_another_pipe_instance(vs, ml);
201 return;
202 }
203
204 DWORD err = GetLastError();
205 U_LOG_E("SetNamedPipeHandleState(PIPE_READMODE_MESSAGE | PIPE_WAIT) failed: %d %s", err, ipc_winerror(err));
206 ipc_server_handle_failure(vs);
207}
208
209
210/*
211 *
212 * Exported functions
213 *
214 */
215
216void
217ipc_server_mainloop_poll(struct ipc_server *vs, struct ipc_server_mainloop *ml)
218{
219 IPC_TRACE_MARKER();
220
221 if (_kbhit()) {
222 U_LOG_E("console input! exiting...");
223 ipc_server_handle_shutdown_signal(vs);
224 return;
225 }
226
227 if (!ml->pipe_handle) {
228 create_another_pipe_instance(vs, ml);
229 }
230 if (!ml->pipe_handle) {
231 return; // Errors already logged.
232 }
233
234 if (ConnectNamedPipe(ml->pipe_handle, nullptr)) {
235 DWORD err = GetLastError();
236 U_LOG_E("ConnectNamedPipe unexpected return TRUE treating as failure: %d %s", err, ipc_winerror(err));
237 ipc_server_handle_failure(vs);
238 return;
239 }
240
241 switch (DWORD err = GetLastError()) {
242 case ERROR_PIPE_LISTENING: return;
243 case ERROR_PIPE_CONNECTED: handle_connected_client(vs, ml); return;
244 default:
245 U_LOG_E("ConnectNamedPipe failed: %d %s", err, ipc_winerror(err));
246 ipc_server_handle_failure(vs);
247 return;
248 }
249}
250
251int
252ipc_server_mainloop_init(struct ipc_server_mainloop *ml)
253{
254 IPC_TRACE_MARKER();
255
256 ml->pipe_handle = INVALID_HANDLE_VALUE;
257 ml->pipe_name = nullptr;
258
259 constexpr char pipe_prefix[] = "\\\\.\\pipe\\";
260 constexpr int prefix_len = sizeof(pipe_prefix) - 1;
261 char pipe_name[MAX_PATH + prefix_len];
262 strcpy(pipe_name, pipe_prefix);
263
264 if (u_file_get_path_in_runtime_dir(XRT_IPC_MSG_SOCK_FILENAME, pipe_name + prefix_len, MAX_PATH) == -1) {
265 U_LOG_E("u_file_get_path_in_runtime_dir failed!");
266 return -1;
267 }
268
269 ml->pipe_name = _strdup(pipe_name);
270 if (ml->pipe_name == nullptr) {
271 U_LOG_E("_strdup(\"%s\") failed!", pipe_name);
272 goto err;
273 }
274
275 if (!create_pipe_instance(ml, true)) {
276 U_LOG_E("CreateNamedPipeA \"%s\" first instance failed, see above.", ml->pipe_name);
277 goto err;
278 }
279
280 return 0;
281
282err:
283 ipc_server_mainloop_deinit(ml);
284
285 return -1;
286}
287
288void
289ipc_server_mainloop_deinit(struct ipc_server_mainloop *ml)
290{
291 IPC_TRACE_MARKER();
292
293 if (ml->pipe_handle != INVALID_HANDLE_VALUE) {
294 CloseHandle(ml->pipe_handle);
295 ml->pipe_handle = INVALID_HANDLE_VALUE;
296 }
297 if (ml->pipe_name) {
298 free(ml->pipe_name);
299 ml->pipe_name = nullptr;
300 }
301}