/ src / modules / Workspaces / WorkspacesLib / two_way_pipe_message_ipc.cpp
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  }