A working .NET example using SetEntriesInAcl inter

2019-08-01 01:21发布

问题:

Does anyone has a working example of invoking SetEntriesInAcl method in .NET using P/Invoke?

I keep getting error 87 when invoking it and just cannot get what am I doing wrong.

Here are my definitions:

private enum FileAccessRights
{
  FILE_READ_DATA = 0x0001,
}
private enum AccessMode
{
  GRANT_ACCESS = 1,
  REVOKE_ACCESS = 4,
}

private enum InheritanceFlags
{
  NO_INHERITANCE = 0x0,
}

private enum TrusteeForm
{
  TRUSTEE_IS_SID = 0,
}

private enum TrusteeType
{
  TRUSTEE_IS_USER = 1,
}
private struct ExplicitAccess
{
  public FileAccessRights AccessPermissions;
  public AccessMode AccessMode;
  public InheritanceFlags Inheritance;
  public Trustee Trustee;
}

private struct Trustee
{
  public IntPtr MultipleTrustee;
  public int MultipleTrusteeOperation;
  public TrusteeForm TrusteeForm;
  public TrusteeType TrusteeType;
  [MarshalAs(UnmanagedType.LPWStr)]
  public string Name;
}
[DllImport("advapi32.dll", SetLastError = true)]
static extern int SetEntriesInAcl(int countOfExplicitEntries, ref ExplicitAccess explicitEntry, IntPtr oldAcl, out IntPtr newAcl);

Here is how I invoke it:

    SecurityIdentifier sid = GetSid();
    var ea = new ExplicitAccess
    {
      AccessPermissions = FileAccessRights.FILE_READ_DATA,
      AccessMode = AccessMode.GRANT_ACCESS,
      Inheritance = InheritanceFlags.NO_INHERITANCE,
      Trustee = new Trustee
      {
        TrusteeForm = TrusteeForm.TRUSTEE_IS_SID,
        TrusteeType = TrusteeType.TRUSTEE_IS_USER,
        Name = sid.Value
      }
    };

    IntPtr newAcl;
    int res = SetEntriesInAcl(1, ref ea, currentAcl, out newAcl);

I keep getting error 87 (invalid parameter) and do not know why.

Thanks a lot in advance to all the Good Samaritans out there.

EDIT1

I will be glad to use the new managed API for changing the Acl, if someone shows me how to if I need to modify the Acl of a private key container associated with a certificate. It is unclear how to use the managed API in this scenario.

回答1:

I had the same requirement to do this via Interop calls. I found the relevant answer for which I was searching here:

(different error code, same underlying reason for failure) Using SetEntriesInAcl in C# : error 1332

It has to do with ANSI vs. Unicode support and you'll need to use the correct CharSet on the P-Invoke method definition.

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern uint SetEntriesInAcl(
   int cCountOfExplicitEntries,
   ref EXPLICIT_ACCESS pListOfExplicitEntries,
   IntPtr OldAcl,
   out IntPtr NewAcl);

Another thing to ensure is that the CharSet is consistent for all struct and method definitions. In fact because our services run on servers that all support Unicode character sets, we changed the CharSet to CharSet.Unicode across the board.

Also we needed to ensure that the unmanaged type to which we were marshalling string parameters matched the Unicode character set (i.e. wide-char types in C++/native for example). Below we use UnmanagedType.LPWStr.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct TRUSTEE
{
    public IntPtr MultipleTrustee;
    public MULTIPLE_TRUSTEE_OPERATION MultipleTrusteeOperation;
    public TRUSTEE_FORM TrusteeForm;
    public TRUSTEE_TYPE TrusteeType;
    [MarshalAs(UnmanagedType.LPWStr)] public string Name;
}

[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern uint GetNamedSecurityInfo(
     [MarshalAs(UnmanagedType.LPWStr)] string pObjectName,
     SE_OBJECT_TYPE ObjectType,
     SECURITY_INFORMATION SecurityInfo,
     out IntPtr pSidOwner,
     out IntPtr pSidGroup,
     out IntPtr pDacl,
     out IntPtr pSacl,
     out IntPtr pSecurityDescriptor);

(This is for working code for setting the permissions on a share path but it might help you get to what you need):

public static void SetReadOnlySharePermissions(string serverName, string shareName, string wellKnownGroupName, DfsSharePermission permissions)
    {
        IntPtr sidOwnerPtr = IntPtr.Zero;
        IntPtr groupOwnerPtr = IntPtr.Zero;
        IntPtr saclPtr = IntPtr.Zero;
        IntPtr oldDacl = IntPtr.Zero;
        IntPtr oldSecurityDescriptor = IntPtr.Zero;
        string shareObjectName = $@"\\{serverName}\{shareName}";

        uint securityObjectQueryResult = NetShareInterop.GetNamedSecurityInfo(
            shareObjectName,
            NetShareInterop.SE_OBJECT_TYPE.SE_LMSHARE,
            NetShareInterop.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION,
            out sidOwnerPtr,
            out groupOwnerPtr,
            out oldDacl,
            out saclPtr,
            out oldSecurityDescriptor);

        if (securityObjectQueryResult != 0)
        {
            throw new Win32Exception((int)securityObjectQueryResult);
        }

        // Default permissions = ReadOnly
        uint shareAccessPermissions = (uint)NetShareInterop.ACCESS_MASK.SHARE_ACCESS_READ;
        switch (permissions)
        {
            case DfsSharePermission.FullControl:
                shareAccessPermissions = (uint)NetShareInterop.ACCESS_MASK.SHARE_ACCESS_FULL;
                break;
            case DfsSharePermission.ReadWrite:
                shareAccessPermissions = (uint)NetShareInterop.ACCESS_MASK.SHARE_ACCESS_WRITE;
                break;
        }

        NetShareInterop.EXPLICIT_ACCESS access = new NetShareInterop.EXPLICIT_ACCESS()
        {
            AccessMode = (uint)NetShareInterop.ACCESS_MODE.SET_ACCESS,
            AccessPermissions = shareAccessPermissions,
            Inheritance = (uint)NetShareInterop.ACCESS_INHERITANCE.SUB_CONTAINERS_AND_OBJECTS_INHERIT,
            trustee = new NetShareInterop.TRUSTEE()
            {
                Name = wellKnownGroupName,
                TrusteeForm = NetShareInterop.TRUSTEE_FORM.TRUSTEE_IS_NAME,
                TrusteeType = NetShareInterop.TRUSTEE_TYPE.TRUSTEE_IS_WELL_KNOWN_GROUP
            }
        };

        IntPtr newDacl;
        int initializeAclEntriesResult = NetShareInterop.SetEntriesInAcl(1, ref access, oldDacl, out newDacl);
        if (initializeAclEntriesResult != 0)
        {
            throw new Win32Exception(initializeAclEntriesResult);
        }

        uint setSecurityResult = NetShareInterop.SetNamedSecurityInfo(
            shareObjectName,
            NetShareInterop.SE_OBJECT_TYPE.SE_LMSHARE,
            NetShareInterop.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION,
            IntPtr.Zero,
            IntPtr.Zero,
            newDacl,
            IntPtr.Zero);

        if (setSecurityResult != 0)
        {
            throw new Win32Exception((int)setSecurityResult);
        }
    }

(..and why make you dig for all those silly interop enum and struct definitions?)

internal static class NetShareInterop
{
    private const CharSet DefaultCharSet = CharSet.Unicode;

    internal enum ACCESS_MODE : uint
    {
        NOT_USED_ACCESS = 0,
        GRANT_ACCESS,
        SET_ACCESS,
        REVOKE_ACCESS,
        SET_AUDIT_SUCCESS,
        SET_AUDIT_FAILURE
    }

    internal enum ACCESS_MASK : uint
    {
        GENERIC_ALL = 0x10000000, //268435456,
        GENERIC_READ = 0x80000000, //2147483648L,
        GENERIC_WRITE = 0x40000000, //1073741824,
        GENERIC_EXECUTE = 0x20000000, //536870912,
        STANDARD_RIGHTS_READ = 0x00020000, //131072
        STANDARD_RIGHTS_WRITE = 0x00020000,
        SHARE_ACCESS_READ = 0x1200A9, // 1179817
        SHARE_ACCESS_WRITE = 0x1301BF, // 1245631
        SHARE_ACCESS_FULL = 0x1f01ff // 2032127
    }

    internal enum ACCESS_INHERITANCE : uint
    {
        NO_INHERITANCE = 0,
        OBJECT_INHERIT_ACE = 0x1,
        CONTAINER_INHERIT_ACE = 0x2,
        NO_PROPAGATE_INHERIT_ACE = 0x4,
        INHERIT_ONLY_ACE = 0x8,
        INHERITED_ACE = 0x10,
        SUB_OBJECTS_ONLY_INHERIT = ACCESS_INHERITANCE.OBJECT_INHERIT_ACE | ACCESS_INHERITANCE.INHERIT_ONLY_ACE,
        SUB_CONTAINERS_ONLY_INHERIT = ACCESS_INHERITANCE.CONTAINER_INHERIT_ACE | ACCESS_INHERITANCE.INHERIT_ONLY_ACE,
        SUB_CONTAINERS_AND_OBJECTS_INHERIT = ACCESS_INHERITANCE.CONTAINER_INHERIT_ACE | ACCESS_INHERITANCE.OBJECT_INHERIT_ACE,
    }

    internal enum MULTIPLE_TRUSTEE_OPERATION
    {
        NO_MULTIPLE_TRUSTEE,
        TRUSTEE_IS_IMPERSONATE
    }

    internal enum NetError : uint
    {
        NERR_Success = 0,
        NERR_UnknownDevDir = 0x00000844,
        NERR_RedirectedPath = 0x00000845,
        NERR_DuplicateShare = 0x00000846,
        NERR_NetNameNotFound = 0x00000906,
        NERR_DfsNoSuchVolume = 0x00000A66
    }

    internal enum SE_OBJECT_TYPE
    {
        SE_UNKNOWN_OBJECT_TYPE = 0,
        SE_FILE_OBJECT,
        SE_SERVICE,
        SE_PRINTER,
        SE_REGISTRY_KEY,
        SE_LMSHARE,
        SE_KERNEL_OBJECT,
        SE_WINDOW_OBJECT,
        SE_DS_OBJECT,
        SE_DS_OBJECT_ALL,
        SE_PROVIDER_DEFINED_OBJECT,
        SE_WMIGUID_OBJECT,
        SE_REGISTRY_WOW64_32KEY
    }

    [Flags]
    internal enum SECURITY_INFORMATION : uint
    {
        OWNER_SECURITY_INFORMATION = 0x00000001,
        GROUP_SECURITY_INFORMATION = 0x00000002,
        DACL_SECURITY_INFORMATION = 0x00000004,
        SACL_SECURITY_INFORMATION = 0x00000008,
        UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000,
        UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000,
        PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000,
        PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000
    }

    internal enum SHARE_TYPE : uint
    {
        STYPE_DISKTREE = 0,
        STYPE_PRINTQ = 1,
        STYPE_DEVICE = 2,
        STYPE_IPC = 3,
        STYPE_TEMPORARY = 0x40000000,
        STYPE_SPECIAL = 0x80000000,
    }

    internal enum TRUSTEE_FORM
    {
        TRUSTEE_IS_SID = 0,
        TRUSTEE_IS_NAME,
        TRUSTEE_BAD_FORM,
        TRUSTEE_IS_OBJECTS_AND_SID,
        TRUSTEE_IS_OBJECTS_AND_NAME
    }

    internal enum TRUSTEE_TYPE
    {
        TRUSTEE_IS_UNKNOWN = 0,
        TRUSTEE_IS_USER,
        TRUSTEE_IS_GROUP,
        TRUSTEE_IS_DOMAIN,
        TRUSTEE_IS_ALIAS,
        TRUSTEE_IS_WELL_KNOWN_GROUP,
        TRUSTEE_IS_DELETED,
        TRUSTEE_IS_INVALID,
        TRUSTEE_IS_COMPUTER
    }

    [StructLayout(LayoutKind.Sequential, CharSet = DefaultCharSet)]
    internal struct SHARE_INFO_502
    {
        [MarshalAs(UnmanagedType.LPWStr)]
        public string shi502_netname;
        public SHARE_TYPE shi502_type;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string shi502_remark;
        public int shi502_permissions;
        public int shi502_max_uses;
        public int shi502_current_uses;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string shi502_path;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string shi502_passwd;
        public int shi502_reserved;
        public IntPtr shi502_security_descriptor;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = DefaultCharSet)]
    internal struct EXPLICIT_ACCESS
    {
        public uint AccessPermissions;
        public uint AccessMode;
        public uint Inheritance;
        public TRUSTEE trustee;
    }

    //Platform independent (32 & 64 bit) - use Pack = 0 for both platforms. IntPtr works as well.
    [StructLayout(LayoutKind.Sequential, CharSet = DefaultCharSet)]
    internal struct TRUSTEE
    {
        public IntPtr MultipleTrustee;
        public MULTIPLE_TRUSTEE_OPERATION MultipleTrusteeOperation;
        public TRUSTEE_FORM TrusteeForm;
        public TRUSTEE_TYPE TrusteeType;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string Name;
    }

    [DllImport("advapi32.dll", CharSet = DefaultCharSet, SetLastError = true)]
    internal static extern uint GetNamedSecurityInfo(
       [MarshalAs(UnmanagedType.LPWStr)] string pObjectName,
       SE_OBJECT_TYPE ObjectType,
       SECURITY_INFORMATION SecurityInfo,
       out IntPtr pSidOwner,
       out IntPtr pSidGroup,
       out IntPtr pDacl,
       out IntPtr pSacl,
       out IntPtr pSecurityDescriptor);

    [DllImport("Netapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern NetError NetShareAdd(

        [MarshalAs(UnmanagedType.LPWStr)] string strServer,
        Int32 dwLevel,
        ref SHARE_INFO_502 buf,
        out uint parm_err
    );

    [DllImport("netapi32.dll", CharSet = DefaultCharSet, SetLastError = true)]
    internal static extern NetError NetShareDel(
        [MarshalAs(UnmanagedType.LPWStr)] string strServer,
        [MarshalAs(UnmanagedType.LPWStr)] string strNetName,
        int reserved //must be 0
    );

    [DllImport("advapi32.dll", CharSet = DefaultCharSet, SetLastError = true)]
    internal static extern uint SetNamedSecurityInfo(
         [MarshalAs(UnmanagedType.LPWStr)] string pObjectName,
         SE_OBJECT_TYPE ObjectType,
         SECURITY_INFORMATION SecurityInfo,
         IntPtr psidOwner,
         IntPtr psidGroup,
         IntPtr pDacl,
         IntPtr pSacl);

    [DllImport("advapi32.dll", CharSet = DefaultCharSet, SetLastError = true)]
    internal static extern int SetEntriesInAcl(
         int cCountOfExplicitEntries,
         ref EXPLICIT_ACCESS pListOfExplicitEntries,
         IntPtr OldAcl,
         out IntPtr NewAcl);
}


回答2:

I don't have an answer to your Interop question, but .Net now supports ACL in managed code using the System.Security.AccessControl namespace. For an example see http://msdn.microsoft.com/en-us/library/ms229078.aspx.

This may be easier to use than rolling your own interop.