/ src / common / interop / two_way_pipe_message_ipc.cpp
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  }