How to set an access mask not representable with FileSystemRights (e.g. GENERIC_ALL) when using FileSystemSecurity/DirectorySecurity?
Asked Answered
B

1

2

FileSystemRights is an enum with Flags attribute which is supposed to be used with DirectorySecurity and FileSystemSecurity.

But when you create a FileSystemAccessRule via new you cannot pass an int for the access mask and what's worse, the existing public constructors will check the range of the passed value and raise an exception if the range is exceeded. Which means even a forcible cast such as (FileSystemRights)0x10000000 (for GENERIC_ALL) will compile, but always fail.

So how can one use values for access mask which cannot be represented with FileSystemRights then, such as GENERIC_ALL?


Rationale:

  1. ACEs with GENERIC_ALL as access mask occur in the wild
  2. Since they occur in the wild, one should be able to create them. .NET makes it very hard, though.

The meaning of GENERIC_ALL can certainly be represented by FileSystemRights.FullControl, but in my case it was important to get an ACE with the access mask being set to GENERIC_ALL, not to something that's "effectively the same".

Basifixed answered 16/6, 2021 at 15:42 Comment(0)
B
3

My solution is based on the .NET framework 4.8 reference source an may not necessarily work for other versions. But since the solution involves a function also used when internally reading ACLs from the filesystem, I am optimistic that the interface won't simply be scrapped.

TL;DR

var ace = dirsec.AccessRuleFactory(ident.User, 0x10000000,
    false, InheritanceFlags.None, PropagationFlags.None,
    AccessControlType.Allow)
    as FileSystemAccessRule; // <<<< IMPORTANT PART!

Why does it fail?

The issue is that FileSystemAccessRule only has public constructors which will access the enum FileSystemRights and which will "validate" that by calling a private method named AccessMaskFromRights(). This means even though we can manage to build after forcibly passing (FileSystemRights)0x10000000 to the constructors, we will always get the following exception:

Unhandled Exception: System.ArgumentOutOfRangeException: The value '268435456' is not valid for this usage of the type FileSystemRights.

However, there is an internal constructor which takes int accessMask as second argument instead. This is how the constructor is declared:

internal FileSystemAccessRule(
    IdentityReference identity,
    int accessMask,
    bool isInherited,
    InheritanceFlags inheritanceFlags,
    PropagationFlags propagationFlags,
    AccessControlType type);

That's the one we need to access. But how?

NB: Unfortunately, even though we could create an AccessRule (from which FileSystemAccessRule is derived) the AddAccessRule() method of FileSystemSecurity and DirectorySecurity will not accept it, it only accepts FileSystemAccessRule as input.

How to work around it?

After digging a bit further it turns out FileSystemSecurity does have a method named AccessRuleFactory() which internally also appears to be what's used whenever the FileSystemAccessRule instances get created from actual ACLs read from the file system.

Said method takes an int accessMask as well and - unsurprisingly - has access to the internal constructor mentioned above, which allows us to create a FileSystemAccessRule from an int as opposed to a FileSystemRights (which is simply missing our desired values).

public sealed override AccessRule AccessRuleFactory(
    IdentityReference identityReference,
    int accessMask,
    bool isInherited,
    InheritanceFlags inheritanceFlags,
    PropagationFlags propagationFlags,
    AccessControlType type);

So in essence what we need to do is to have an instance of FileSystemSecurity or DirectorySecurity which we can use to call AccessRuleFactory().

DirectorySecurity dirsec = System.IO.Directory.GetAccessControl(
    System.IO.Path.GetTempPath());
using (WindowsIdentity ident = WindowsIdentity.GetCurrent())
{
    // 0x10000000 == GENERIC_ALL
    var ace = dirsec.AccessRuleFactory(ident.User, 0x10000000,
        false, InheritanceFlags.None, PropagationFlags.None,
        AccessControlType.Allow)
        as FileSystemAccessRule; // <<<< IMPORTANT PART!
    dirsec.AddAccessRule(ace);
    // ... set dirsec on some directory ...
}

This creates a FileSystemAccessRule via the DirectorySecurity.AccessRuleFactory(), giving GENERIC_ALL access to the the user in whose context the current process is running.

Basifixed answered 16/6, 2021 at 15:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.