Invalid ACL error from SetKernelObjectSecurity API call

I need to change the security descriptor of a process to either deny or allow certain users to kill a process.  The application must do some critical operations that do not happen (of course) when the process is killed.

The code below always returns "The Access control list (ACL) structure is invalid." after the SetKernelObjectSecurity call.  

If I pass in nothing for the ACL when I create my RawSecurityDescriptor, the call to SetKernelObjectSecurity succeeds.  However, since no ACL exists, it gives everyone full control of the process, something that really shouldn't happen.  This has been verified by using ProcessExplorer.

I am at wits end trying to figure out what is wrong with the ACL.  

Imports System.Runtime.InteropServices
 
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> Public Structure ACL
   Public AclRevision As Byte
   Public Sbz1 As Byte
   Public AclSize As Short
   Public AceCount As Short
   Public Sbz2 As Short
End Structure
 
<StructLayout(LayoutKind.Sequential)> Public Structure SECURITY_DESCRIPTOR
   Public Revision As Byte
   Public Sbz1 As Byte
   Public Control As Integer
   Public Owner As IntPtr
   Public Group As IntPtr
   Public Sacl As IntPtr
   Public Dacl As IntPtr
End Structure
 
 
 
Public Class Form1
 
   <DllImport("advapi32.dll", CharSet:=CharSet.Ansi, setlasterror:=True)> _
   Public Shared Function SetKernelObjectSecurity( _
      ByVal hObject As IntPtr, _
      ByVal SecurityInformation As Integer, _
      <MarshalAs(UnmanagedType.Struct)> ByRef pSecurityDescriptor As SECURITY_DESCRIPTOR) _
      As Integer
 
   End Function
 
   Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
      Dim LastError As Integer = 0
      Dim iResult As Integer = 0
 
      Dim myACL As New System.Security.AccessControl.RawAcl(1, 1)
      Dim newACE As System.Security.AccessControl.GenericAce
      Dim myByte((4 * 12) - 1) As Byte
 
      Dim myAccessMask As Integer = 0
      Dim mySid As New System.Security.Principal.SecurityIdentifier(Security.Principal.WellKnownSidType.BuiltinAdministratorsSid, Nothing)
 
 
      newACE = New System.Security.AccessControl.CommonAce(Security.AccessControl.AceFlags.None, _
                                                            Security.AccessControl.AceQualifier.AccessDenied, _
                                                            myAccessMask, _
                                                            mySid, _
                                                            False, _
                                                            myByte)
 
 
      myACL.InsertAce(0, newACE)
 
      Dim mySD As New System.Security.AccessControl.RawSecurityDescriptor( _
         Security.AccessControl.ControlFlags.DiscretionaryAclPresent, _
         Nothing, _
         Nothing, _
         Nothing, _
         myACL)  ' acl goes here
      ' If you change "myACL" to nothing, it removes the SD/ACLs from the process
      ' which means everyone has full control--don't want that.
 
 
      Dim intptrACL As IntPtr = IntPtr.Zero
      Dim descACL(myACL.BinaryLength - 1) As Byte
      myACL.GetBinaryForm(descACL, 0)
      intptrACL = Marshal.AllocHGlobal(descACL.Length)
      Marshal.Copy(descACL, 0, intptrACL, descACL.Length)
      Dim testACL As New ACL
      testACL = Marshal.PtrToStructure(intptrACL, testACL.GetType)
 
 
      Dim intptrSD As IntPtr = IntPtr.Zero
      Dim desc(mySD.BinaryLength - 1) As Byte
      mySD.GetBinaryForm(desc, 0)
      intptrSD = Marshal.AllocHGlobal(desc.Length)
      Marshal.Copy(desc, 0, intptrSD, desc.Length)
      Dim testSD As New SECURITY_DESCRIPTOR
      testSD = Marshal.PtrToStructure(intptrSD, testSD.GetType)
 
      iResult = SetKernelObjectSecurity(System.Diagnostics.Process.GetCurrentProcess.Handle, &H4, testSD)
      If (iResult = 0) Then
         ' Always returns "The Access control list (ACL) structure is invalid."
         LastError = Marshal.GetLastWin32Error
         MessageBox.Show(New System.ComponentModel.Win32Exception(LastError).ToString, "SetKernelObjectSecurity")
      Else
         MessageBox.Show("Worked", "SetKernelObjectSecurity")
      End If
 
      ' Clean up
      Marshal.FreeHGlobal(intptrSD)
      Marshal.FreeHGlobal(intptrACL)
 
   End Sub
End Class

Open in new window

kekalAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

grayeCommented:
I'd recommend that you start with the retrieving the existing SD on the process first... that way it will "fill in the blanks" with all of the other parts of the DS that have nothing to do with the DACL
After you've got the "before" SD you can just modify the DACL and then write back the "after" SD
0
kekalAuthor Commented:
Thanks for the suggestion.  However, it will not work because the .NET framework does not give me any methods to get the SecurityDescriptor from a Process (unless Im missing something.)  Even if I could, I may run in to the same problem because I need to assign permissions to other users that do not have current ACLs.  For example, if Steve runs a process, I may need to give Sally (a non-administrator) the permission to kill it.

In the code above, I am using BuiltinAdministratorsSid as an example, but the SID may be from any user.  

I can get the code to Deny terminate to all users by calling the SetSecurityInfo API  (see code snippet below)&but this is only a partial solution since I may need to give the Terminate right (PROCESS_TERMINATE) to a other users.  Using the example above, I would create an explicit deny ACL for Steve and an explicit PROCESS_TERMINATE ACL for Sally.  

Thanks
Kekal

iResult = SetSecurityInfo(myProcessHandle, _
          SE_OBJECT_TYPE.SE_KERNEL_OBJECT, _
          SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, _
          Nothing, _
          Nothing, _
          intptrACL, _
          Nothing)

Open in new window

0
grayeCommented:
Yes, so just use the GET version of GetSecurityInfo or GetKernelObjectSecurity to retrieve the existing SD
Take a look at the article as it describes the steps required by build or modify a DACL using the low-level APIs
http://www.emmet-gray.com/Articles/SetPermissions.htm 
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Fundamentals of JavaScript

Learn the fundamentals of the popular programming language JavaScript so that you can explore the realm of web development.

kekalAuthor Commented:
The information in the link provided only works for named objects, such as files.

The code (according to the return value) succeeds.  However, I don't know how to turn the IntPtrs in for the Sid, Dacl, and SD in to something useful, like a .NET RawACL object.

I know RawACL has a constructor that accepts a byte array.  However, I do not know how to turn the Dacl pointer in to a byte array.  Marshal.Copy should work for me, except that I do not know how long the DACL is!  

If you uncomment the AllocHGlobal lines, I get something back.
        Dim iResult As Integer
        Dim ipSidOwner, ipSidGroup, ipDacl, ipSacl, ipSD As New IntPtr
        ipSidGroup = IntPtr.Zero
        ipSidOwner = IntPtr.Zero
        ipDacl = IntPtr.Zero
        ipSacl = IntPtr.Zero
        ipSD = IntPtr.Zero
        Dim mySD As New SECURITY_DESCRIPTOR
 
 
        'ipSidOwner = Marshal.AllocHGlobal(100)
        'ipDacl = Marshal.AllocHGlobal(100)
        'ipSD = Marshal.AllocHGlobal(100)
 
        iResult = GetSecurityInfo( _
                System.Diagnostics.Process.GetCurrentProcess.Handle, _
                SE_OBJECT_TYPE.SE_KERNEL_OBJECT, _
                SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION Or SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, _
                ipSidOwner, _
                Nothing, _
                ipDacl, _
                Nothing, _
                ipSD)
        DisplayError(iResult, "GetSecurityInfo", True)

Open in new window

0
grayeCommented:
You don't need to "turn them into something useful"... they already are useful.
Looking back at that example, the flow of the program would be:
  • get the Security Descriptor and DACL
  • build an explicit access structure
  • merge this Explict Access with the existing DACL
  • write the new Security Descriptor/DACL back
  • clean up and go home

     
0
kekalAuthor Commented:
Hey....you were right!!!  I couldn't see the forest for the trees and was making life too complicated.  It would be nice to use the managed objects, but that is not a requirement.  Thanks!

I did find that BuildExplicitAccessWithName does NOT appear to work on a x64 system (compiled with the Any CPU setting).  Using the code from the link above (without modification as a test), it produces an application crash when running outside of the VS IDE.  In the IDE in either debug or release modes, the application just exits without errors.
0
kekalAuthor Commented:
All...

This solution may not work on x64 bit systems.  For more information, do an EE search on BuildExplicitAccessWithName.

I found that the app crash goes away if you turn optimizations off.  I'll try to post more here once (if) I found a solution.  We may have found a Microsoft bug.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
.NET Programming

From novice to tech pro — start learning today.