4

I have a C# client application that connects to a C++ server application using Pipes. When I try to connect I get the error: System.UnauthorizedAccessException: Access to the path is denied.

After looking this up, I saw that I can fix it by creating a PipeSecurity object and adding a PipeAccessRule. But this only works if the server is also a C# application.

Any idea how I can fix this access problem if I have the server as C++ application?

I searched already but can't find a solution.

C#:

      int timeOut = 500;
      NamedPipeClientStream pipeStream = new NamedPipeClientStream(".", pipeName, PipeDirection.Out, PipeOptions.Asynchronous);
      pipeStream.Connect(timeOut);  

      byte[] buffer = Encoding.UTF8.GetBytes(sendStr);
      pipeStream.BeginWrite(buffer, 0, buffer.Length, new AsyncCallback(AsyncSend), pipeStream);

C++:

   _hPipe = ::CreateNamedPipe(configurePipeName(getPipeName()).c_str(),
                            PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,
                            PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
                            1,
                            bufferSize,
                            bufferSize,
                            NMPWAIT_USE_DEFAULT_WAIT,
                            NULL);

  if (_hPipe == INVALID_HANDLE_VALUE)
  {
    logStream << "CreateNamedPipe failed for " << sys::OperatingSystem::getLastErrorMessage() << blog::over;
    return;
  }

  HANDLE ioEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
  overlapped.hEvent = ioEvent;

  assert(overlapped.hEvent);
  if (ioEvent == INVALID_HANDLE_VALUE)
  {
    logStream << "CreateEvent failed for " << sys::OperatingSystem::getLastErrorMessage() << blog::over;
    return;
  }

  while (!terminating())
  {
    BOOL connected = false;
    DWORD waitMessage;
    DWORD timeOut = 700;

    if (!::ConnectNamedPipe(_hPipe, &overlapped))
    {
      switch (::GetLastError()) 
      {
        case ERROR_PIPE_CONNECTED:
          connected = true;
          break;

        case ERROR_IO_PENDING:
          waitMessage = ::WaitForSingleObject(overlapped.hEvent, timeOut);
          if (waitMessage == WAIT_OBJECT_0)
          {
            DWORD dwIgnore;
            BOOL conn = (::GetOverlappedResult(_hPipe, &overlapped, &dwIgnore, TRUE));
            if (conn)
              connected = true;
            else
              logStream << "ConnectedNamedPipe reported an error: " << sys::OperatingSystem::getLastErrorMessage() << blog::over;
          }
          else
            ::CancelIo(_hPipe);
          break;

        default:
          logStream << "ConnectedNamedPipe reported an error: " << sys::OperatingSystem::getLastErrorMessage() << blog::over;
      }
    }

    if(connected)
    {
      if (::ReadFile(_hPipe, buffer, sizeof(buffer) - 1, &size, NULL))
      {
        buffer[size] = '\0';
        std::string receivedMessage(buffer);
        // if message is received from client, setdirty to call detectDisplay.
        if (clientUniqueMessage.compare(receivedMessage) == 0)
          setDirty();
        else
          logStream << "Incoming message from client does not match with the expected message." << blog::over;
      }
      else
        logStream << "ReadFile failed. " << sys::OperatingSystem::getLastErrorMessage() << blog::over;

    }
    ::DisconnectNamedPipe(_hPipe);
  }
}
Tee
  • 385
  • 3
  • 14
  • 1
    Does the C++ server application run as a Windows service? – Garland Jan 08 '18 at 23:28
  • Yes. I am running the C++ application as a service on windows – Tee Jan 09 '18 at 07:38
  • Possible duplicate of [NamedPipeClientStream can not access to NamedPipeServerStream under session 0](https://stackoverflow.com/questions/13174660/namedpipeclientstream-can-not-access-to-namedpipeserverstream-under-session-0) – orhtej2 Jan 09 '18 at 17:42
  • @orhtej2 That actually solves the problem only if the client and the server are both C# application. Please always read the question clearly. – Tee Jan 11 '18 at 07:15

1 Answers1

1

The last parameter of the [CreateNamedPipe function] specifies:

lpSecurityAttributes [in, optional] A pointer to a SECURITY_ATTRIBUTES structure that specifies a security descriptor for the new named pipe and determines whether child processes can inherit the returned handle. If lpSecurityAttributes is NULL, the named pipe gets a default security descriptor and the handle cannot be inherited. The ACLs in the default security descriptor for a named pipe grant full control to the LocalSystem account, administrators, and the creator owner. They also grant read access to members of the Everyone group and the anonymous account.

The C++ named pipe server application has Administrator privilege since it is running as a Windows service under the LocalSystem account. The C# client application running as a Standard user does not have Administrator privilege. By default (lpSecurityAttributes is NULL), the C# client application has only read access to the named pipe created by the C++ server running as a service.

As a quick test, if you run the C# client application as Administrator, it should be able to communicate with the C++ server application successfully.

To fix the problem, the C++ server application needs to create a security descriptor for the named pipe object and grant write access to it for Everyone. Please see MSDN example for creating a security descriptor.

I wrote a class NamedPipeServerStream before for my C++ server application running as a service. Here is the part of the code related to creating the named pipe for your reference.

NamedPipeServerStream::NamedPipeServerStream(const std::string & pipeName, const unsigned pipeBufferSize /*= PIPE_BUFFER_SIZE*/)
    : m_hPipe(INVALID_HANDLE_VALUE)
    , m_pipeName(PIPE_NAME_ROOT + pipeName)
    , m_bConnected(false)
{
    PSID pEveryoneSID = NULL;
    PSID pAdminSID = NULL;
    PACL pACL = NULL;
    EXPLICIT_ACCESS ea[2];
    SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
    SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
    SECURITY_ATTRIBUTES sa;
    SCOPE_GUARD{
        if (pEveryoneSID) { FreeSid(pEveryoneSID); }
        if (pAdminSID) { FreeSid(pAdminSID); }
        if (pACL) { LocalFree(pACL); }
    };

    // Create a well-known SID for the Everyone group.
    if (!AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pEveryoneSID))
    {
        throw std::runtime_error("AllocateAndInitializeSid failed, GLE=" + std::to_string(GetLastError()));
    }
    // Initialize an EXPLICIT_ACCESS structure for an ACE.
    SecureZeroMemory(&ea, 2 * sizeof(EXPLICIT_ACCESS));
    // The ACE will allow Everyone full access to the key.
    ea[0].grfAccessPermissions = FILE_ALL_ACCESS | GENERIC_WRITE | GENERIC_READ;  
    ea[0].grfAccessMode = SET_ACCESS;
    ea[0].grfInheritance = NO_INHERITANCE;
    ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
    ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
    ea[0].Trustee.ptstrName = (LPTSTR)pEveryoneSID;

    // Create a SID for the BUILTIN\Administrators group.
    if (!AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminSID))
    {
        throw std::runtime_error("AllocateAndInitializeSid failed, GLE=" + std::to_string(GetLastError()));
    }
    // The ACE will allow the Administrators group full access to the key.
    ea[1].grfAccessPermissions = FILE_ALL_ACCESS | GENERIC_WRITE | GENERIC_READ;   
    ea[1].grfAccessMode = SET_ACCESS;
    ea[1].grfInheritance = NO_INHERITANCE;
    ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
    ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
    ea[1].Trustee.ptstrName = (LPTSTR)pAdminSID;

    // Create a new ACL that contains the new ACEs.
    DWORD dwRes = SetEntriesInAclW(2, ea, NULL, &pACL);
    if (ERROR_SUCCESS != dwRes)
    {
        throw std::runtime_error("SetEntriesInAcl failed, GLE=" + std::to_string(GetLastError()));
    }
    // Initialize a security descriptor.  
    auto secDesc = std::vector<unsigned char>(SECURITY_DESCRIPTOR_MIN_LENGTH);
    PSECURITY_DESCRIPTOR pSD = (PSECURITY_DESCRIPTOR)(&secDesc[0]);
    if (nullptr == pSD)
    {
        throw std::runtime_error("LocalAlloc failed, GLE=" + std::to_string(GetLastError()));
    }
    if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION))
    {
        throw std::runtime_error("InitializeSecurityDescriptor failed, GLE=" + std::to_string(GetLastError()));
    }
    // Add the ACL to the security descriptor. 
    if (!SetSecurityDescriptorDacl(pSD, TRUE, pACL, FALSE))   // not a default DACL 
    {
        throw std::runtime_error("SetSecurityDescriptorDacl failed, GLE=" + std::to_string(GetLastError()));
    }
    // Initialize a security attributes structure.
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = pSD;
    sa.bInheritHandle = FALSE;

    // Finally to create the pipe.
    m_hPipe = CreateNamedPipeA(
        m_pipeName.c_str(),             // pipe name 
        PIPE_ACCESS_DUPLEX,       // read/write access 
        PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE |        // Byte Stream type pipe 
        PIPE_WAIT,                // blocking mode 
        PIPE_UNLIMITED_INSTANCES, // max. instances  
        pipeBufferSize,                  // output buffer size 
        pipeBufferSize,                  // input buffer size 
        0,                        // client time-out 
        &sa);                    // default security attribute 

    if (!IsPipeCreated())
    {
        throw std::runtime_error("CreateNamedPipe failed, GLE=" + std::to_string(GetLastError()));
    }
}

bool NamedPipeServerStream::IsPipeCreated() const
{
    return (INVALID_HANDLE_VALUE != m_hPipe);
}
Garland
  • 911
  • 7
  • 22
  • This actually solved the problem, by creating a security descriptor. Thanks! – Tee Jan 11 '18 at 07:13
  • My colleague has encountered a tough program similar with this one(https://stackoverflow.com/questions/60431348/ipc-uwp-c-sharp-pipe-client-fails-on-connect-c-server). It's namedpipe between a UWP client and C++ server. Could you take a look at it and give some opinions? Thanks! @Garland – Tom Xue Feb 28 '20 at 07:48
  • Where does SCOPE_GUARD come from? Is there a Windows header file with this defined? Thanks. – Mark Uebel Jun 22 '20 at 17:31
  • The SCOPE_GUARD is my own scope guard class and macro written before GSL. Now you may use GSL, for example, `auto scopeGuard = gsl::finally([&]() noexcept { LocalFree(imageInfoBuffer); });` – Garland Jun 22 '20 at 22:16