The open source OpenXR runtime
at main 302 lines 8.4 kB view raw
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}