0

I have a program which is composed of a Windows Service and a GUI. The windows service runs under a local system account, and the GUI runs on a user account and receives information about the service through a named pipe.

The issue I'm having is the pipe can't communicate with the GUI unless the GUI was run with admin privileges. If I try to access the pipe without admin privileges, I get an access to path is denied error. I have also tried passing in a PipeSecurity value, however then I get an error saying The operation has timed out.

Here is the code I'm running with the PipeSecurity option passed in;

private async Task ListenForPipeRequests(CancellationToken token)
{
    while (!token.IsCancellationRequested) {
        PipeSecurity security = CreatePipeSecurity();
        
        using (var server = NamedPipeServerStreamAcl.Create(
            "my_unique_pipe_name",
            PipeDirection.InOut,
            1, // maxNumberOfServerInstances
            PipeTransmissionMode.Byte,
            PipeOptions.Asynchronous,
            1024, // inBufferSize
            1024, // outBufferSize
            security, // pipeSecurity
            HandleInheritability.Inheritable,
            PipeAccessRights.FullControl))
        {            
            try
            {
                await server.WaitForConnectionAsync(token);

                using (var reader = new StreamReader(server))
                using (var writer = new StreamWriter(server) { AutoFlush = true })
                {
                    string? message;
                    while ((message = await reader.ReadLineAsync()) != null)
                    {
                        // Process the received message
                        string response2 = HandleRequest(message);

                        // Optionally send a response
                        await writer.WriteLineAsync(response2);
                    }
                }

            }
            catch (OperationCanceledException ex)
            {
                // Handle cancellation
                SaveToLog("Cancelation: " + ex.Message);
            }
            catch (IOException ioex)
            {
                // Handle pipe broken or other IO exceptions
                SaveToLog("IO Exception: " + ioex.Message);
            }
            catch (Exception ex)
            {
                SaveToLog("Exception: " + ex.Message);
            }
        }
    }
}

private PipeSecurity CreatePipeSecurity()
{
    var pipeSecurity = new PipeSecurity();

    // Grant full control to the current user
    var currentUser = WindowsIdentity.GetCurrent().User;
    pipeSecurity.AddAccessRule(new PipeAccessRule(
        currentUser,
        PipeAccessRights.FullControl,
        AccessControlType.Allow));

    // Grant full control to the Local System account
    pipeSecurity.AddAccessRule(new PipeAccessRule(
        new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null),
        PipeAccessRights.FullControl,
        AccessControlType.Allow));

    // Grant full control to Administrators
    pipeSecurity.AddAccessRule(new PipeAccessRule(
        new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null),
        PipeAccessRights.FullControl,
        AccessControlType.Allow));

    // Grant read/write access to everyone
    pipeSecurity.AddAccessRule(new PipeAccessRule(
        new SecurityIdentifier(WellKnownSidType.WorldSid, null),
        PipeAccessRights.ReadWrite,
        AccessControlType.Allow));

    return pipeSecurity;
}

When I'm not debugging with Acl, I'm just using the NamedPipeStream like this

using (var server = new NamedPipeServerStream("my_unique_pipe_name", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous))
{ ... }

However as mentioned this doesn't work when I don't access the pipe from an application running under admin rights, instead giving me an access to path denied error.

Does anyone have any thought on what could be causing my issue? This seems like it should be a common issue, however I can't find any threads that use a recent version of .NET. The AddAccessRules are perhaps overly generous right now, but that's because I can't figure out what's wrong. I'm installing the service using sc.exe, and I'm running the gui using the visual studio debugger if that matters.

I really appreciate any input you have; thanks!

3
  • WellKnownSidType.AuthenticatedUserSid ? stackoverflow.com/questions/51546328/… Commented Jun 27 at 4:39
  • It looks like I can't pass in the PipeSecurity variable to the constructor anymore in .NET 8. I did try the SetAccessRule instead of AddAccessRule; I had no luck but I'm going to keep looking into it. Unfortunately it feels like they change how this works every time they upgrade .NET which has made this really confusing
    – haxonek
    Commented Jun 27 at 5:44
  • Actually that may have been the fix, I just had to use the NamedPipeServerStreamAcl.Create function instead of NamedPipeServerStream, and then I had to get rid of the HandleInheritability.Inheritable, PipeAccessRights.FullControl variables from the constructor which were in my example. Otherwise it works now!
    – haxonek
    Commented Jun 27 at 5:59

1 Answer 1

0

Ultimately I found my issue was I had included HandleInheritability.Inheritable and PipeAccessRights.FullControl as variables into the Create constructor, which were causing my issue. It was saying something about the operation was not allowed and the program was failing as a result.

Here is what I have that works:

private async Task ListenForPipeRequests(CancellationToken token)
{
    while (!token.IsCancellationRequested) {
        try
        {
            using (var server = NamedPipeServerStreamAcl.Create(
                "my_unique_pipe_name",
                PipeDirection.InOut,
                1, // maxNumberOfServerInstances
                PipeTransmissionMode.Byte,
                PipeOptions.Asynchronous,
                2048, // inBufferSize
                2048, // outBufferSize
                security)) // pipeSecurity
            {
    
                try
                {
                    server.WaitForConnection();

                    using (var reader = new StreamReader(server))
                    using (var writer = new StreamWriter(server) { AutoFlush = true })
                    {
                        string? message;
                        while ((message = reader.ReadLine()) != null)
                        {
                            // Process the received message
                            string response2 = HandleRequest(message);

                            // Optionally send a response
                            writer.WriteLine(response2);
                        }
                    }

                }
                catch (OperationCanceledException ex)
                {
                    // Handle cancellation
                    SaveToLog("Cancelation: " + ex.Message);
                }
                catch (IOException ioex)
                {
                    // Handle pipe broken or other IO exceptions
                    SaveToLog("IO Exception: " + ioex.Message);
                }
                catch (Exception ex)
                {
                    SaveToLog("Exception: " + ex.Message);
                }
            }
        }
        catch (Exception ex)
        {
            SaveToLog("Pipe Error: " + ex.Message);
        }
    }
}

private PipeSecurity CreatePipeSecurity()
{
    var pipeSecurity = new PipeSecurity();

    // Grant read/write access to everyone
    pipeSecurity.AddAccessRule(new PipeAccessRule(
        new SecurityIdentifier(WellKnownSidType.WorldSid, null),
        PipeAccessRights.ReadWrite,
        AccessControlType.Allow));

    return pipeSecurity;
}

Specifically I had to use NamedPipeServerStreamAcl.Create, not NamedPipeServerStream, which no longer has a constructor in .NET 8. Additionally I found it was more reliable if I used synchronous transmission instead of async; in particular, after the first two transmissions async would start to fail and it wasn't clear why.

Lastly trying to set the PipeSecurity after creating a NamedPipeServerStream didn't work. While something like server.SetAccessControl(security) will compile it will also crash out after running.

Not the answer you're looking for? Browse other questions tagged or ask your own question.