#include "pch.h" #include "two_way_pipe_message_ipc_impl.h" #include constexpr DWORD BUFSIZE = 1024; TwoWayPipeMessageIPC::TwoWayPipeMessageIPC( std::wstring _input_pipe_name, std::wstring _output_pipe_name, callback_function p_func) : impl(new TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl( _input_pipe_name, _output_pipe_name, p_func)) { } TwoWayPipeMessageIPC::~TwoWayPipeMessageIPC() { delete impl; } void TwoWayPipeMessageIPC::send(std::wstring msg) { impl->send(msg); } void TwoWayPipeMessageIPC::start(HANDLE _restricted_pipe_token) { impl->start(_restricted_pipe_token); } void TwoWayPipeMessageIPC::end() { impl->end(); } TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::TwoWayPipeMessageIPCImpl( std::wstring _input_pipe_name, std::wstring _output_pipe_name, callback_function p_func) { input_pipe_name = _input_pipe_name; output_pipe_name = _output_pipe_name; dispatch_inc_message_function = p_func; } void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::send(std::wstring msg) { output_queue.queue_message(msg); } void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::start(HANDLE _restricted_pipe_token) { output_queue_thread = std::thread(&TwoWayPipeMessageIPCImpl::consume_output_queue_thread, this); input_queue_thread = std::thread(&TwoWayPipeMessageIPCImpl::consume_input_queue_thread, this); input_pipe_thread = std::thread(&TwoWayPipeMessageIPCImpl::start_named_pipe_server, this, _restricted_pipe_token); } void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::end() { closed = true; input_queue.interrupt(); input_queue_thread.join(); output_queue.interrupt(); output_queue_thread.join(); pipe_connect_handle_mutex.lock(); if (current_connect_pipe_handle != NULL) { //Cancels the Pipe currently waiting for a connection. CancelIoEx(current_connect_pipe_handle, NULL); } pipe_connect_handle_mutex.unlock(); input_pipe_thread.join(); } void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::send_pipe_message(std::wstring message) { // Adapted from https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipe-client HANDLE output_pipe_handle; const wchar_t* message_send = message.c_str(); BOOL fSuccess = FALSE; DWORD cbToWrite, cbWritten, dwMode; const wchar_t* lpszPipename = output_pipe_name.c_str(); // Try to open a named pipe; wait for it, if necessary. while (1) { output_pipe_handle = CreateFile( lpszPipename, // pipe name GENERIC_READ | // read and write access GENERIC_WRITE, 0, // no sharing NULL, // default security attributes OPEN_EXISTING, // opens existing pipe 0, // default attributes NULL); // no template file // Break if the pipe handle is valid. if (output_pipe_handle != INVALID_HANDLE_VALUE) break; // Exit if an error other than ERROR_PIPE_BUSY occurs. DWORD curr_error = 0; if ((curr_error = GetLastError()) != ERROR_PIPE_BUSY) { return; } // All pipe instances are busy, so wait for 20 seconds. if (!WaitNamedPipe(lpszPipename, 20000)) { return; } } dwMode = PIPE_READMODE_MESSAGE; fSuccess = SetNamedPipeHandleState( output_pipe_handle, // pipe handle &dwMode, // new pipe mode NULL, // don't set maximum bytes NULL); // don't set maximum time if (!fSuccess) { return; } // Send a message to the pipe server. cbToWrite = (lstrlen(message_send)) * sizeof(WCHAR); // no need to send final '\0'. Pipe is in message mode. fSuccess = WriteFile( output_pipe_handle, // pipe handle message_send, // message cbToWrite, // message length &cbWritten, // bytes written NULL); // not overlapped if (!fSuccess) { return; } CloseHandle(output_pipe_handle); return; } void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::consume_output_queue_thread() { while (!closed) { std::wstring message = output_queue.pop_message(); if (message.length() == 0) { break; } send_pipe_message(message); } } BOOL TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::GetLogonSID(HANDLE hToken, PSID* ppsid) { // From https://docs.microsoft.com/en-us/previous-versions/aa446670(v=vs.85) BOOL bSuccess = FALSE; DWORD dwIndex; DWORD dwLength = 0; PTOKEN_GROUPS ptg = NULL; // Verify the parameter passed in is not NULL. if (NULL == ppsid) goto Cleanup; // Get required buffer size and allocate the TOKEN_GROUPS buffer. if (!GetTokenInformation( hToken, // handle to the access token TokenGroups, // get information about the token's groups (LPVOID)ptg, // pointer to TOKEN_GROUPS buffer 0, // size of buffer &dwLength // receives required buffer size )) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) goto Cleanup; ptg = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength); if (ptg == NULL) goto Cleanup; } // Get the token group information from the access token. if (!GetTokenInformation( hToken, // handle to the access token TokenGroups, // get information about the token's groups (LPVOID)ptg, // pointer to TOKEN_GROUPS buffer dwLength, // size of buffer &dwLength // receives required buffer size )) { goto Cleanup; } // Loop through the groups to find the logon SID. for (dwIndex = 0; dwIndex < ptg->GroupCount; dwIndex++) if ((ptg->Groups[dwIndex].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID) { // Found the logon SID; make a copy of it. dwLength = GetLengthSid(ptg->Groups[dwIndex].Sid); *ppsid = (PSID)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength); if (*ppsid == NULL) goto Cleanup; if (!CopySid(dwLength, *ppsid, ptg->Groups[dwIndex].Sid)) { HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid); goto Cleanup; } break; } bSuccess = TRUE; Cleanup: // Free the buffer for the token groups. if (ptg != NULL) HeapFree(GetProcessHeap(), 0, (LPVOID)ptg); return bSuccess; } VOID TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::FreeLogonSID(PSID* ppsid) { // From https://docs.microsoft.com/en-us/previous-versions/aa446670(v=vs.85) HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid); } int TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::change_pipe_security_allow_restricted_token(HANDLE handle, HANDLE token) { PACL old_dacl, new_dacl; PSECURITY_DESCRIPTOR sd; EXPLICIT_ACCESS ea; PSID user_restricted; int error; if (!GetLogonSID(token, &user_restricted)) { error = 5; // No access error. goto Ldone; } if (GetSecurityInfo(handle, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &old_dacl, NULL, &sd)) { error = GetLastError(); goto Lclean_sid; } memset(&ea, 0, sizeof(EXPLICIT_ACCESS)); ea.grfAccessPermissions |= GENERIC_READ | FILE_WRITE_ATTRIBUTES; ea.grfAccessPermissions |= GENERIC_WRITE | FILE_READ_ATTRIBUTES; ea.grfAccessPermissions |= SYNCHRONIZE; ea.grfAccessMode = SET_ACCESS; ea.grfInheritance = NO_INHERITANCE; ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ea.Trustee.ptstrName = (LPTSTR)user_restricted; if (SetEntriesInAcl(1, &ea, old_dacl, &new_dacl)) { error = GetLastError(); goto Lclean_sd; } if (SetSecurityInfo(handle, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, new_dacl, NULL)) { error = GetLastError(); goto Lclean_dacl; } error = 0; Lclean_dacl: LocalFree((HLOCAL)new_dacl); Lclean_sd: LocalFree((HLOCAL)sd); Lclean_sid: FreeLogonSID(&user_restricted); Ldone: return error; } HANDLE TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::create_medium_integrity_token() { HANDLE restricted_token_handle; SAFER_LEVEL_HANDLE level_handle = NULL; DWORD sid_size = SECURITY_MAX_SID_SIZE; BYTE medium_sid[SECURITY_MAX_SID_SIZE]; if (!SaferCreateLevel(SAFER_SCOPEID_USER, SAFER_LEVELID_NORMALUSER, SAFER_LEVEL_OPEN, &level_handle, NULL)) { return NULL; } if (!SaferComputeTokenFromLevel(level_handle, NULL, &restricted_token_handle, 0, NULL)) { SaferCloseLevel(level_handle); return NULL; } SaferCloseLevel(level_handle); if (!CreateWellKnownSid(WinMediumLabelSid, nullptr, medium_sid, &sid_size)) { CloseHandle(restricted_token_handle); return NULL; } TOKEN_MANDATORY_LABEL integrity_level = { 0 }; integrity_level.Label.Attributes = SE_GROUP_INTEGRITY; integrity_level.Label.Sid = reinterpret_cast(medium_sid); if (!SetTokenInformation(restricted_token_handle, TokenIntegrityLevel, &integrity_level, sizeof(integrity_level))) { CloseHandle(restricted_token_handle); return NULL; } return restricted_token_handle; } void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::handle_pipe_connection(HANDLE input_pipe_handle) { if (!input_pipe_handle) { return; } constexpr DWORD readBlockBytes = BUFSIZE; std::wstring message; size_t iBlock = 0; message.reserve(BUFSIZE); bool ok; do { constexpr size_t charsPerBlock = readBlockBytes / sizeof(message[0]); message.resize(message.size() + charsPerBlock); DWORD bytesRead = 0; ok = ReadFile( input_pipe_handle, // read the message directly into the string block by block simultaneously resizing it message.data() + iBlock * charsPerBlock, readBlockBytes, &bytesRead, nullptr); if (!ok && GetLastError() != ERROR_MORE_DATA) { break; } iBlock++; } while (!ok); // trim the message's buffer const auto nullCharPos = message.find_last_not_of(L'\0'); if (nullCharPos != std::wstring::npos) { message.resize(nullCharPos + 1); } input_queue.queue_message(std::move(message)); // Flush the pipe to allow the client to read the pipe's contents // before disconnecting. Then disconnect the pipe, and close the // handle to this pipe instance. FlushFileBuffers(input_pipe_handle); DisconnectNamedPipe(input_pipe_handle); CloseHandle(input_pipe_handle); } void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::start_named_pipe_server(HANDLE token) { // Adapted from https://docs.microsoft.com/en-us/windows/win32/ipc/multithreaded-pipe-server const wchar_t* pipe_name = input_pipe_name.c_str(); BOOL connected = FALSE; HANDLE connect_pipe_handle = INVALID_HANDLE_VALUE; while (!closed) { { std::unique_lock lock(pipe_connect_handle_mutex); connect_pipe_handle = CreateNamedPipe( pipe_name, PIPE_ACCESS_DUPLEX | WRITE_DAC, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE, 0, NULL); if (connect_pipe_handle == INVALID_HANDLE_VALUE) { return; } if (token != NULL) { int err = change_pipe_security_allow_restricted_token(connect_pipe_handle, token); } current_connect_pipe_handle = connect_pipe_handle; } connected = ConnectNamedPipe(connect_pipe_handle, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); { std::unique_lock lock(pipe_connect_handle_mutex); current_connect_pipe_handle = NULL; } if (connected) { std::thread(&TwoWayPipeMessageIPCImpl::handle_pipe_connection, this, connect_pipe_handle).detach(); } else { // Client could not connect. CloseHandle(connect_pipe_handle); } } } void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::consume_input_queue_thread() { while (!closed) { outgoing_message = L""; std::wstring message = input_queue.pop_message(); if (message.length() == 0) { break; } // Check if callback method exists first before trying to call it. // otherwise just store the response message in a variable. if (dispatch_inc_message_function != nullptr) { dispatch_inc_message_function(message); } outgoing_message = message; } }