Code Mortem

"Old code never dies...you have to kill it." - Grady Booch

Saturday, January 07, 2006

Creating a Null DACL in Managed Code

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 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);
Marshal.Copy(desc, 0, sa.lpSecurityDescriptor, desc.Length);

// use the structure
CreateNamedPipe( yada, yada, yada,..., sa);

// Important!! Be sure to clean up after you use the structure
Marshal.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.

9 Comments:

At 5/20/2008 11:39 AM, Blogger Magallo said...

You're great! Is just what I was looking for! I haven't tried yet, but I hope it helps me in this trouble! THANKS A LOT!!!

 
At 6/05/2008 10:23 AM, Anonymous Anonymous said...

THANK YOU VERY MUCH!!!
I post (for other Googlers) working code I derived from yours for CreateFileMapping (to be called from C# Windows Service):

[StructLayout(LayoutKind.Sequential)]
class SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}

...

#region Creating a Null DACL

// Creating a Null DACL: http://codemortem.blogspot.com/2006/01/creating-null-dacl-in-managed-code.html
// using it for CreateFileMapping: http://www.dotnet247.com/247reference/msgs/52/260930.aspx
// object to IntPtr options: http://blogs.msdn.com/jmstall/archive/2006/10/09/gchandle_5F00_intptr.aspx
// doing AddrOfPinnedObject: http://www.codeproject.com/KB/files/fastbinaryfileinput.aspx

// 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);
Marshal.Copy(desc, 0, sa.lpSecurityDescriptor, desc.Length);

#endregion

try
{
// Make sure that the Garbage Collector doesn't move our sa
GCHandle saHandle = GCHandle.Alloc(sa, GCHandleType.Pinned);
try
{
nativeHandle = Win32Native.CreateFileMapping(
(IntPtr)Win32Native.INVALID_HANDLE_VALUE,
saHandle.AddrOfPinnedObject(),
Win32Native.ProtectionLevel.PAGE_READWRITE,
0,
size,
name);
}
finally
{
saHandle.Free(); // Give control of the sa back to the GC
}
}
finally
{
Marshal.FreeHGlobal(sa.lpSecurityDescriptor);
sa.lpSecurityDescriptor = IntPtr.Zero;
}

 
At 8/16/2008 4:38 AM, Anonymous Anonymous said...

THANK YOU!!!

"How many times have I told you? You're NOT ALLOWED to create a NULL DACL! Bad programmer! <whacks hands with ruler...> Bad programmer! <whack>"

LOL!

 
At 9/28/2008 4:32 PM, Anonymous Anonymous said...

THANK YOU!!!

It also works with GlobalSempahpores between 'NT-AUTORITY\NETWORK' and a windows user.

 
At 10/13/2008 9:58 AM, Anonymous Anonymous said...

I found this blog article very useful .. except that you need a pointer to the SECURITY_ATTRIBUTES. This is how I created my named pipe:

SECURITY_DESCRIPTOR sd = new SECURITY_DESCRIPTOR();
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
IntPtr lpSecurityDescriptor;
IntPtr lpSA;

//Set up SECURITY_ATTRIBUTES structure
sa.nLength = Marshal.SizeOf(sa);
sa.bInheritHandle = 1;

//Set up lpSecurityDescriptor IntPtr
lpSecurityDescriptor = Marshal.AllocCoTaskMem(Marshal.SizeOf(sd));
Marshal.StructureToPtr(sd, lpSecurityDescriptor, false);

//Set permission on lpSecurityDescriptor
InitializeSecurityDescriptor(lpSecurityDescriptor, 1);
SetSecurityDescriptorDacl(lpSecurityDescriptor, true, IntPtr.Zero, false);

//Assign it back to the SECURITY_ATTRIBUTES structure (sa)
sa.lpSecurityDescriptor = lpSecurityDescriptor;

//Now create a IntPtr for sa
lpSA = Marshal.AllocCoTaskMem(Marshal.SizeOf(sa));
Marshal.StructureToPtr(sa, lpSA, false);

SafeFileHandle clientHandle =
CreateNamedPipe(
this.pipeName,
DUPLEX | FILE_FLAG_OVERLAPPED,
0,
255,
BUFFER_SIZE,
BUFFER_SIZE,
0,
lpSA); //Send in the pointer to the SECURITY_ATTRIBUTES structure (sa)

Tom Sprows

 
At 12/12/2008 1:21 PM, Blogger phil said...

This is the ONLY functional example I could find. I'm implementing a named pipe from C# windows service... this code saved me so much time. THANK YOU!!

 
At 2/16/2010 5:27 PM, Blogger Hailton said...

Thank you very very much to this article.
This help me fully in my problem.
My application have a Service running as a SYSTEM account that communicates with many user's loggeds. Every users have a process whitch communicats Service. But it did not work when user don't was a Adm. Now it works
^^
Thanks again

 
At 4/09/2011 9:07 PM, Anonymous Josip Medved said...

You saved me a lot of time. Thanks.

 
At 6/06/2011 7:39 AM, Blogger voloda said...

Thanks. Very useful

 

Post a Comment

Links to this post:

Create a Link

<< Home