runtime: “Attempted to perform an unauthorized operation” when calling NamedPipeServerStream.SetAccessControl

I’m having some trouble securing a NamedPipeServerStream from a .NET Standard lib. I opened a question on stack overflow here.

The gist is that when I try to call SetAccessControl using either of the listed ways, I get an “Attempted to perform an unauthorized operation”:

  • PipesAclExtensions.SetAccessControl(pipeServer, pipeSecurity);
  • pipeServer.SetAccessControl(pipeSecurity);

Here’s a gist of the problem.

PipeSecurity pipeSecurity = new PipeSecurity();

WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);

if (principal.IsInRole(WindowsBuiltInRole.Administrator))
{
    // Allow the Administrators group full access to the pipe.
    pipeSecurity.AddAccessRule(new PipeAccessRule(
        new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null).Translate(typeof(NTAccount)),
        PipeAccessRights.FullControl, AccessControlType.Allow));
} else {
    // Allow AuthenticatedUser read and write access to the pipe.
    pipeSecurity.AddAccessRule(new PipeAccessRule(
        WindowsIdentity.GetCurrent().User,
        PipeAccessRights.ReadWrite, AccessControlType.Allow));
}

var pipeServer =
    new NamedPipeServerStream(
        "mypipe",
        PipeDirection.InOut,
        1,
        PipeTransmissionMode.Byte,
        PipeOptions.Asynchronous);

// Both of these throw
PipesAclExtensions.SetAccessControl(pipeServer, pipeSecurity);
// or
pipeServer.SetAccessControl(pipeSecurity);

The exception I’m getting is:

Attempted to perform an unauthorized operation. at System.Security.AccessControl.Win32.SetSecurityInfo(ResourceType type, String name, SafeHandle handle, SecurityInfos securityInformation, SecurityIdentifier owner, SecurityIdentifier group, GenericAcl sacl, GenericAcl dacl) at System.Security.AccessControl.NativeObjectSecurity.Persist(String name, SafeHandle handle, AccessControlSections includeSections, Object exceptionContext) at System.Security.AccessControl.NativeObjectSecurity.Persist(SafeHandle handle, AccessControlSections includeSections, Object exceptionContext) at System.IO.Pipes.PipeSecurity.Persist(SafeHandle handle)

NOTE: I am pulling in the System.IO.Pipes.AccessControl nuget package

Am I doing something wrong?

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 5
  • Comments: 33 (23 by maintainers)

Most upvoted comments

I noticed that the tests for the extension methods all use the empty PipeSecurity() constructor so I tried using an empty one in my code to see if this could be a bug that was overlooked since the tests never added an access rule.

Sure enough, if I pass in an empty PipeSecurity into SetAccessControl, my code works:

PipesAclExtensions.SetAccessControl(pipeServer, new PipeSecurity());

From what I know about Named Pipes, when you create them, you can pass in a Security object that will create all the ACLs you want but you can’t update them once created. This means that SetAccessControl probably won’t work for NamedPipes.

In the .NET Framework, there was a ctor for NamePipeServerStream that allowed you to pass in a PipeSecurity but that doesn’t seem to exist in .NET Core (probably because everything is split up now).

I’m really hoping there’s some sort of solution for this out in the wild as I need it in .NET Core 2.0 - otherwise I’ll have to go down the pinvoke route to create a Pipe that way.

Straight to the relevant comment in the PR on dotnet/standard https://github.com/dotnet/standard/pull/826/files#r202885532

Does it work cross plat (Win, macOS, linux)?

Yes it does work cross plat. In Windows we use ACL and in Unix we use PeerID to get the socket’s owner ID and check if it is the same user trying to connect:

https://github.com/dotnet/corefx/blob/master/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Unix.cs#L91-L105

Does CurrentUserOnly prevent the following scenario (privilege escalation) :

Yes it does, in Windows ACL will handle this by itself, and in Unix whenever you elevate you’re now a different user (root) so the PeerID and current user ID should differ.

Just as a guide this are the tests for this feature: https://github.com/dotnet/corefx/blob/master/src/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.netcoreapp.cs https://github.com/dotnet/corefx/blob/master/src/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.netcoreapp.Unix.cs https://github.com/dotnet/corefx/blob/master/src/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.netcoreapp.Windows.cs

Just a general comment, and please ignore if foolish as I am a complete newbie to core and standard. Lack of ACL support (or a consistent replacement) in .netstandard seems problematic to me. I’ve just had a try at porting a medium sized communications library written in .net framework and it all went fairly painlessly until security. Named mutexs, memory mapped files, and named pipes, all need an ACL on windows to prevent userland privilege escalation, and that issue is going to be there on any OS. It just seems a little perverse that the only way I could get things to work right now is to turn off security.

I’d like to target .netstandard through most of our components, but IPC mechanisms are at the base of the stack, and if they can’t be secured, they can’t be used, and the whole house of cards falls down.

That’s a long winded way of saying, please add CurrentUserOnly to standard .next. Ideally all these constructors that went missing with the security Param would come back. In magical unicorn land there would be an object security abstraction that would work across platforms. I’m very very ignorant, but surely the concept of users, roles and permissions crosses platforms. The ACL just seems so pivotal to security on windows that it came as a shock that the replacement is either missing, or not obvious, in .net standard.

CurrentUserOnly is much better than nothing. At least this prevents a trust boundary violation.

I ended up going the p/invoke route and borrowed most of the code from PowerShell… https://github.com/PowerShell/PowerShellEditorServices/blob/1031e2296449ab30bb4968e0285566a33e4bf9f4/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs#L136-L274

It’s ugly… But at least our pipes are secure. -CurrentUser would do the trick… But I have to support back to .NET Core 2.0.

One possible way to make this work is to add to S.IO.Pipes.AccessControl some static factory methods that can create named pipes with PipeSecurity and PipeAccessRights equivalent to the constructors that are in the Fx but not in the standard - this will allow the same old functionality of the Fx. There should be some refactor to avoid/reduce code duplication but in principle I don’t see a reason for it not to work.

OTOH S.IO.Pipes.AccessControl seems to never ever actually worked with anything other than the empty PipeSecurity and since this is the first time that we are realizing that I’m questioning if it is worth to fix… likely we can’t just drop the package for compat reasons: there seems to be a number of projects referencing it but I can’t see any useful usage of it as it is.

@tylerl0706 if you can provide some more info about your scenario and what you are trying to achieve? It can be a helpful data point.

/cc @danmosemsft @stephentoub @weshaggard @safern

Talked offline, part of the problem is that ACL’s are not even part of the base shared package so having NPSS reference ACL’s pulls a lot of code into the core install.

@pjanotti hopefully can help brainstorm possible solutions for what you want to do.