two_way_pipe_message_ipc.cpp
1 #include "pch.h" 2 3 #include <common/interop/two_way_pipe_message_ipc_impl.h> 4 5 #include <iterator> 6 7 constexpr DWORD BUFSIZE = 1024; 8 9 TwoWayPipeMessageIPC::TwoWayPipeMessageIPC( 10 std::wstring _input_pipe_name, 11 std::wstring _output_pipe_name, 12 callback_function p_func) : 13 impl(new TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl( 14 _input_pipe_name, 15 _output_pipe_name, 16 p_func)) 17 { 18 } 19 20 TwoWayPipeMessageIPC::~TwoWayPipeMessageIPC() 21 { 22 delete impl; 23 } 24 25 void TwoWayPipeMessageIPC::send(std::wstring msg) 26 { 27 impl->send(msg); 28 } 29 30 void TwoWayPipeMessageIPC::start(HANDLE _restricted_pipe_token) 31 { 32 impl->start(_restricted_pipe_token); 33 } 34 35 void TwoWayPipeMessageIPC::end() 36 { 37 impl->end(); 38 } 39 40 TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::TwoWayPipeMessageIPCImpl( 41 std::wstring _input_pipe_name, 42 std::wstring _output_pipe_name, 43 callback_function p_func) 44 { 45 input_pipe_name = _input_pipe_name; 46 output_pipe_name = _output_pipe_name; 47 dispatch_inc_message_function = p_func; 48 } 49 50 void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::send(std::wstring msg) 51 { 52 output_queue.queue_message(msg); 53 } 54 55 void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::start(HANDLE _restricted_pipe_token) 56 { 57 output_queue_thread = std::thread(&TwoWayPipeMessageIPCImpl::consume_output_queue_thread, this); 58 input_queue_thread = std::thread(&TwoWayPipeMessageIPCImpl::consume_input_queue_thread, this); 59 input_pipe_thread = std::thread(&TwoWayPipeMessageIPCImpl::start_named_pipe_server, this, _restricted_pipe_token); 60 } 61 62 void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::end() 63 { 64 closed = true; 65 input_queue.interrupt(); 66 input_queue_thread.join(); 67 output_queue.interrupt(); 68 output_queue_thread.join(); 69 pipe_connect_handle_mutex.lock(); 70 if (current_connect_pipe_handle != NULL) 71 { 72 //Cancels the Pipe currently waiting for a connection. 73 CancelIoEx(current_connect_pipe_handle, NULL); 74 } 75 pipe_connect_handle_mutex.unlock(); 76 input_pipe_thread.join(); 77 } 78 79 void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::send_pipe_message(std::wstring message) 80 { 81 // Adapted from https://learn.microsoft.com/windows/win32/ipc/named-pipe-client 82 HANDLE output_pipe_handle; 83 const wchar_t* message_send = message.c_str(); 84 BOOL fSuccess = FALSE; 85 DWORD cbToWrite, cbWritten, dwMode; 86 const wchar_t* lpszPipename = output_pipe_name.c_str(); 87 88 // Try to open a named pipe; wait for it, if necessary. 89 90 while (1) 91 { 92 output_pipe_handle = CreateFile( 93 lpszPipename, // pipe name 94 GENERIC_READ | // read and write access 95 GENERIC_WRITE, 96 0, // no sharing 97 NULL, // default security attributes 98 OPEN_EXISTING, // opens existing pipe 99 0, // default attributes 100 NULL); // no template file 101 102 // Break if the pipe handle is valid. 103 104 if (output_pipe_handle != INVALID_HANDLE_VALUE) 105 break; 106 107 // Exit if an error other than ERROR_PIPE_BUSY occurs. 108 DWORD curr_error = 0; 109 if ((curr_error = GetLastError()) != ERROR_PIPE_BUSY) 110 { 111 return; 112 } 113 114 // All pipe instances are busy, so wait for 20 seconds. 115 116 if (!WaitNamedPipe(lpszPipename, 20000)) 117 { 118 return; 119 } 120 } 121 dwMode = PIPE_READMODE_MESSAGE; 122 fSuccess = SetNamedPipeHandleState( 123 output_pipe_handle, // pipe handle 124 &dwMode, // new pipe mode 125 NULL, // don't set maximum bytes 126 NULL); // don't set maximum time 127 if (!fSuccess) 128 { 129 return; 130 } 131 132 // Send a message to the pipe server. 133 134 cbToWrite = (lstrlen(message_send)) * sizeof(WCHAR); // no need to send final '\0'. Pipe is in message mode. 135 136 fSuccess = WriteFile( 137 output_pipe_handle, // pipe handle 138 message_send, // message 139 cbToWrite, // message length 140 &cbWritten, // bytes written 141 NULL); // not overlapped 142 if (!fSuccess) 143 { 144 return; 145 } 146 CloseHandle(output_pipe_handle); 147 return; 148 } 149 150 void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::consume_output_queue_thread() 151 { 152 while (!closed) 153 { 154 std::wstring message = output_queue.pop_message(); 155 if (message.length() == 0) 156 { 157 break; 158 } 159 send_pipe_message(message); 160 } 161 } 162 163 BOOL TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::GetLogonSID(HANDLE hToken, PSID* ppsid) 164 { 165 // From https://learn.microsoft.com/previous-versions/aa446670(v=vs.85) 166 BOOL bSuccess = FALSE; 167 DWORD dwIndex; 168 DWORD dwLength = 0; 169 PTOKEN_GROUPS ptg = NULL; 170 171 // Verify the parameter passed in is not NULL. 172 if (NULL == ppsid) 173 goto Cleanup; 174 175 // Get required buffer size and allocate the TOKEN_GROUPS buffer. 176 177 if (!GetTokenInformation( 178 hToken, // handle to the access token 179 TokenGroups, // get information about the token's groups 180 static_cast<LPVOID>(ptg), // pointer to TOKEN_GROUPS buffer 181 0, // size of buffer 182 &dwLength // receives required buffer size 183 )) 184 { 185 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) 186 goto Cleanup; 187 188 ptg = static_cast<PTOKEN_GROUPS>(HeapAlloc(GetProcessHeap(), 189 HEAP_ZERO_MEMORY, 190 dwLength)); 191 192 if (ptg == NULL) 193 goto Cleanup; 194 } 195 196 // Get the token group information from the access token. 197 198 if (!GetTokenInformation( 199 hToken, // handle to the access token 200 TokenGroups, // get information about the token's groups 201 static_cast<LPVOID>(ptg), // pointer to TOKEN_GROUPS buffer 202 dwLength, // size of buffer 203 &dwLength // receives required buffer size 204 )) 205 { 206 goto Cleanup; 207 } 208 209 // Loop through the groups to find the logon SID. 210 211 for (dwIndex = 0; dwIndex < ptg->GroupCount; dwIndex++) 212 if ((ptg->Groups[dwIndex].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID) 213 { 214 // Found the logon SID; make a copy of it. 215 216 dwLength = GetLengthSid(ptg->Groups[dwIndex].Sid); 217 *ppsid = static_cast<PSID>(HeapAlloc(GetProcessHeap(), 218 HEAP_ZERO_MEMORY, 219 dwLength)); 220 if (*ppsid == NULL) 221 goto Cleanup; 222 if (!CopySid(dwLength, *ppsid, ptg->Groups[dwIndex].Sid)) 223 { 224 HeapFree(GetProcessHeap(), 0, static_cast<LPVOID>(*ppsid)); 225 goto Cleanup; 226 } 227 break; 228 } 229 230 bSuccess = TRUE; 231 232 Cleanup: 233 234 // Free the buffer for the token groups. 235 236 if (ptg != NULL) 237 HeapFree(GetProcessHeap(), 0, static_cast<LPVOID>(ptg)); 238 239 return bSuccess; 240 } 241 242 VOID TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::FreeLogonSID(PSID* ppsid) 243 { 244 // From https://learn.microsoft.com/previous-versions/aa446670(v=vs.85) 245 HeapFree(GetProcessHeap(), 0, static_cast<LPVOID>(*ppsid)); 246 } 247 248 int TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::change_pipe_security_allow_restricted_token(HANDLE handle, HANDLE token) 249 { 250 PACL old_dacl, new_dacl; 251 PSECURITY_DESCRIPTOR sd; 252 EXPLICIT_ACCESS ea; 253 PSID user_restricted; 254 int error; 255 256 if (!GetLogonSID(token, &user_restricted)) 257 { 258 error = 5; // No access error. 259 goto Ldone; 260 } 261 262 if (GetSecurityInfo(handle, 263 SE_KERNEL_OBJECT, 264 DACL_SECURITY_INFORMATION, 265 NULL, 266 NULL, 267 &old_dacl, 268 NULL, 269 &sd)) 270 { 271 error = GetLastError(); 272 goto Lclean_sid; 273 } 274 275 memset(&ea, 0, sizeof(EXPLICIT_ACCESS)); 276 ea.grfAccessPermissions |= GENERIC_READ | FILE_WRITE_ATTRIBUTES; 277 ea.grfAccessPermissions |= GENERIC_WRITE | FILE_READ_ATTRIBUTES; 278 ea.grfAccessPermissions |= SYNCHRONIZE; 279 ea.grfAccessMode = SET_ACCESS; 280 ea.grfInheritance = NO_INHERITANCE; 281 ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; 282 ea.Trustee.TrusteeType = TRUSTEE_IS_USER; 283 ea.Trustee.ptstrName = static_cast<LPTSTR>(user_restricted); 284 285 if (SetEntriesInAcl(1, &ea, old_dacl, &new_dacl)) 286 { 287 error = GetLastError(); 288 goto Lclean_sd; 289 } 290 291 if (SetSecurityInfo(handle, 292 SE_KERNEL_OBJECT, 293 DACL_SECURITY_INFORMATION, 294 NULL, 295 NULL, 296 new_dacl, 297 NULL)) 298 { 299 error = GetLastError(); 300 goto Lclean_dacl; 301 } 302 303 error = 0; 304 305 Lclean_dacl: 306 LocalFree(static_cast<HLOCAL>(new_dacl)); 307 Lclean_sd: 308 LocalFree(static_cast<HLOCAL>(sd)); 309 Lclean_sid: 310 FreeLogonSID(&user_restricted); 311 Ldone: 312 return error; 313 } 314 315 HANDLE TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::create_medium_integrity_token() 316 { 317 HANDLE restricted_token_handle; 318 SAFER_LEVEL_HANDLE level_handle = NULL; 319 DWORD sid_size = SECURITY_MAX_SID_SIZE; 320 BYTE medium_sid[SECURITY_MAX_SID_SIZE]; 321 if (!SaferCreateLevel(SAFER_SCOPEID_USER, SAFER_LEVELID_NORMALUSER, SAFER_LEVEL_OPEN, &level_handle, NULL)) 322 { 323 return NULL; 324 } 325 if (!SaferComputeTokenFromLevel(level_handle, NULL, &restricted_token_handle, 0, NULL)) 326 { 327 SaferCloseLevel(level_handle); 328 return NULL; 329 } 330 SaferCloseLevel(level_handle); 331 332 if (!CreateWellKnownSid(WinMediumLabelSid, nullptr, medium_sid, &sid_size)) 333 { 334 CloseHandle(restricted_token_handle); 335 return NULL; 336 } 337 338 TOKEN_MANDATORY_LABEL integrity_level = { 0 }; 339 integrity_level.Label.Attributes = SE_GROUP_INTEGRITY; 340 integrity_level.Label.Sid = reinterpret_cast<PSID>(medium_sid); 341 342 if (!SetTokenInformation(restricted_token_handle, TokenIntegrityLevel, &integrity_level, sizeof(integrity_level))) 343 { 344 CloseHandle(restricted_token_handle); 345 return NULL; 346 } 347 348 return restricted_token_handle; 349 } 350 351 void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::handle_pipe_connection(HANDLE input_pipe_handle) 352 { 353 if (!input_pipe_handle) 354 { 355 return; 356 } 357 constexpr DWORD readBlockBytes = BUFSIZE; 358 std::wstring message; 359 size_t iBlock = 0; 360 message.reserve(BUFSIZE); 361 bool ok; 362 do 363 { 364 constexpr size_t charsPerBlock = readBlockBytes / sizeof(message[0]); 365 message.resize(message.size() + charsPerBlock); 366 DWORD bytesRead = 0; 367 ok = ReadFile( 368 input_pipe_handle, 369 // read the message directly into the string block by block simultaneously resizing it 370 message.data() + iBlock * charsPerBlock, 371 readBlockBytes, 372 &bytesRead, 373 nullptr); 374 375 if (!ok && GetLastError() != ERROR_MORE_DATA) 376 { 377 break; 378 } 379 iBlock++; 380 } while (!ok); 381 // trim the message's buffer 382 const auto nullCharPos = message.find_last_not_of(L'\0'); 383 if (nullCharPos != std::wstring::npos) 384 { 385 message.resize(nullCharPos + 1); 386 } 387 388 input_queue.queue_message(std::move(message)); 389 390 // Flush the pipe to allow the client to read the pipe's contents 391 // before disconnecting. Then disconnect the pipe, and close the 392 // handle to this pipe instance. 393 394 FlushFileBuffers(input_pipe_handle); 395 DisconnectNamedPipe(input_pipe_handle); 396 CloseHandle(input_pipe_handle); 397 } 398 399 void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::start_named_pipe_server(HANDLE token) 400 { 401 // Adapted from https://learn.microsoft.com/windows/win32/ipc/multithreaded-pipe-server 402 const wchar_t* pipe_name = input_pipe_name.c_str(); 403 BOOL connected = FALSE; 404 HANDLE connect_pipe_handle = INVALID_HANDLE_VALUE; 405 while (!closed) 406 { 407 { 408 std::unique_lock lock(pipe_connect_handle_mutex); 409 connect_pipe_handle = CreateNamedPipe( 410 pipe_name, 411 PIPE_ACCESS_DUPLEX | 412 WRITE_DAC, 413 PIPE_TYPE_MESSAGE | 414 PIPE_READMODE_MESSAGE | 415 PIPE_WAIT, 416 PIPE_UNLIMITED_INSTANCES, 417 BUFSIZE, 418 BUFSIZE, 419 0, 420 NULL); 421 422 if (connect_pipe_handle == INVALID_HANDLE_VALUE) 423 { 424 return; 425 } 426 427 if (token != NULL) 428 { 429 change_pipe_security_allow_restricted_token(connect_pipe_handle, token); 430 } 431 current_connect_pipe_handle = connect_pipe_handle; 432 } 433 connected = ConnectNamedPipe(connect_pipe_handle, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); 434 { 435 std::unique_lock lock(pipe_connect_handle_mutex); 436 current_connect_pipe_handle = NULL; 437 } 438 if (connected) 439 { 440 std::thread(&TwoWayPipeMessageIPCImpl::handle_pipe_connection, this, connect_pipe_handle).detach(); 441 } 442 else 443 { 444 // Client could not connect. 445 CloseHandle(connect_pipe_handle); 446 } 447 } 448 } 449 450 void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::consume_input_queue_thread() 451 { 452 while (!closed) 453 { 454 outgoing_message = L""; 455 std::wstring message = input_queue.pop_message(); 456 if (message.length() == 0) 457 { 458 break; 459 } 460 461 // Check if callback method exists first before trying to call it. 462 // otherwise just store the response message in a variable. 463 if (dispatch_inc_message_function != nullptr) 464 { 465 dispatch_inc_message_function(message); 466 } 467 outgoing_message = message; 468 } 469 }