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