While the .NET 2.0 framework libraries bring a lot of added value to the original libraries, the documentation at this point leaves a great deal to be desired. One of the challenges that I faced recently was how to create a null DACL for some interop code. The new framework library includes some great classes for setting security on Registry keys, files and synchronization objects, but if you try to go beyond that, watch out. The documentation will provide you no help. I figured this out by spending several hours experimenting with the and by using
Reflector to look at the IL inside the Microsoft binaries.
I was trying to create a named pipe in managed code. I'm still surprised that the managed libraries have no support for this yet, but be that as it may, I created the obligatory P/Invoke definition for CreateNamedPipe and called it from my code. That worked fine, but I discovered that the default privileges assigned to the pipe were not adequate so I needed to open up access to my pipe. A DACL is a Discretionary Access Control List in Windows. It is a list of ACE (Access Control Entries) that grant or deny access for a group of users to the associated resource. Every file in Windows (on NTFS) has such a list as do most kernel objects. A null DACL is basically a blank check. If your resource has a null DACL, then everyone has complete access to the resource. Sometimes this is referred to as an AEFA (Allow Everyone Full Access) ACE. This is not the same as an empty DACL; in fact it is the opposite. An empty DACL denies everyone any access to the resource.
Now, I must insert a disclaimer. Somebody will surely respond that I shouldn't be creating a null DACL, because it is insecure. That is not the point of this post. I had a good reason to need open access to my pipe, and I am assuming that anyone wanting to use this technique would as well. This technique is expandable beyond NULL DACL's anyway.
So, let's move on. There are two challenges. One, is that we need to figure out how to create the null DACL using the framework libraries in managed code, and second, we need to figure out how to marshal the information successfully over into the unmanaged API. Here is the P/Invoke declaration for CreateNamedPipe.
[DllImport("kernel32.dll", SetLastError = true)]static extern IntPtr CreateNamedPipe(string lpName, uint dwOpenMode, uint dwPipeMode, uint nMaxInstances, uint nOutBufferSize, uint nInBufferSize, uint nDefaultTimeOut, SECURITY_ATTRIBUTES lpSecurityAttributes);I've highlighted the last parameter because that is the one that I care about right now. The lpSecurityAttributes parameter determines what security the caller on the other end of the named pipe must have in order to connect to the pipe.
Here is the interoperable declaration for the SECURITY_ATTRIBUTES structure.
[StructLayout(LayoutKind.Sequential)]class SECURITY_ATTRIBUTES { int nLength; IntPtr lpSecurityDescriptor; int bInheritHandle;}Now, how do we create the security descriptor that is needed for this structure? The security descriptor is what contains the null DACL. The framework does supply us with a class for this called RawSecurityDescriptor. Here is the line of code to create the descriptor with a null DACL.
RawSecurityDescriptor gsd = new RawSecurityDescriptor(ControlFlags.DiscretionaryAclPresent, null, null, null, null);
Yep, that's all for that part. The tricky thing is that you have to set the DACLPresent flag even though you pass null in for the DACL (It's one of the other parameters to the constructor). This makes the class create a null DACL. If you don't pass in this flag you will get a restricted DACL that is set to the login of the current user and the Administrator only.
Now that the first step is done, how do we pass the RawSecurityDescriptor in the SECURITY_ATTRIBUTES structure to the CreateNamedPipe function? This is a bit messy, but it works.
// Build NULL DACL (Allow everyone full access)RawSecurityDescriptor gsd = new RawSecurityDescriptor(ControlFlags.DiscretionaryAclPresent, null, null, null, null);// Construct SECURITY_ATTRIBUTES structureSECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();sa.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));sa.bInheritHandle = 1;// Get binary form of the security descriptor and copy it into placebyte[] desc = new byte[gsd.BinaryLength];gsd.GetBinaryForm(desc, 0);sa.lpSecurityDescriptor = Marshal.AllocHGlobal(desc.Length); Marshal.Copy(desc, 0, sa.lpSecurityDescriptor, desc.Length);// use the structureCreateNamedPipe( yada, yada, yada,..., sa);// Important!! Be sure to clean up after you use the structureMarshal.FreeHGlobal(sa.lpSecurityDescriptor);sa.lpSecurityDescriptor = IntPtr.Zero;This code creates the null DACL and then copies the binary representation into memory where the SECURITY_ATTRIBUTES structure can reference it. Make sure you free the memory after you complete with the structure. I built the free into the Finalizer of the SECURITY_ATTRIBUTES class, but don't wait on the Finalizer to be invoked. I used the Disposable pattern as well to make sure that the memory was freed. Here is a complete listing of the class.
[StructLayout(LayoutKind.Sequential)]internal class SECURITY_ATTRIBUTES : IDisposable { internal int nLength; internal IntPtr lpSecurityDescriptor; internal int bInheritHandle; public static SECURITY_ATTRIBUTES GetNullDacl() { // Build NULL DACL (Allow everyone full access) RawSecurityDescriptor gsd = new RawSecurityDescriptor(ControlFlags.DiscretionaryAclPresent, null, null, null, null); // Construct SECURITY_ATTRIBUTES structure SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES(); sa.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES)); sa.bInheritHandle = 1; // Get binary form of the security descriptor and copy it into place byte[] desc = new byte[gsd.BinaryLength]; gsd.GetBinaryForm(desc, 0); sa.lpSecurityDescriptor = Marshal.AllocHGlobal(desc.Length); // This Alloc is Freed by the Disposer or Finalizer Marshal.Copy(desc, 0, sa.lpSecurityDescriptor, desc.Length); return sa; } public void Dispose() { lock (this) { if (lpSecurityDescriptor != IntPtr.Zero) { Marshal.FreeHGlobal(lpSecurityDescriptor); lpSecurityDescriptor = IntPtr.Zero; } } } ~SECURITY_ATTRIBUTES() { Dispose(); }}I hope this helps. Maybe somebody else will be luckier than me and happen upon this post rather than having to rediscover the method.