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