winapismb

SMB access mask - MAXIMUM_ALLOWED vs GENERIC_ALL


According to this web site about SMB access masks,

  1. MAXIMUM_ALLOWED (0x02000000) - This value indicates that the client is requesting an open to the file with the highest level of access the client has on this file. If no access is granted for the client on this file, the server MUST fail the open with STATUS_ACCESS_DENIED.
  2. GENERIC_ALL (0x10000000) - This value indicates a request for all the access flags that are previously listed, except MAXIMUM_ALLOWED and ACCESS_SYSTEM_SECURITY.

I'd like to know what are common use cases for using these. Specifically, if I wanted to be maximally permissive, shouldn't I use GENERIC_ALL? And though it says in its description that it doesn't include MAXIMUM_ALLOWED, because it includes the previously mentioned access flags wouldn't GENERIC_ALL therefore also subsume MAXIMUM_ALLOWED? I have a feeling I'm missing some important concept.


Solution

  • Okay, so technically this is a Windows question, not sure why that tag is missing. The meaning of those two values differs, but you also should be aware of another related concept: GENERIC_MAPPING.

    The generic mapping allows to turn generic access masks (for read, write and all) into object-type-specific access masks. As the name implies, these are specific to the object type, i.e. file, pipe, memory mapped file ("Section"), mutex ("Mutant") etc. You can use WinObj from Sysinternals to get a sense of the object types available in Windows. However, you just need to be aware that this exists and there is no need to know all the details. This becomes relevant only when you are using the ACL editor and need to do that mapping yourself, for example. Every object type basically comes with a mapping like that.

    After obtaining a handle to an object, you can query OBJECT_BASIC_INFORMATION (public prototype here; alternative) to see what level of object-type-specific access was granted.

    Obtaining the handle is the part where the access check happens. This is why passing around or inheriting handles can be an issue, sometimes. So if the SD/DACL says you don't have the requested level of access or outright denies it, you won't even get a handle and instead will get to see STATUS_ACCESS_DENIED.

    The difference

    Now that we have this out of the way let's discuss the difference between GENERIC_ALL and MAXIMUM_ALLOWED.

    If you paid attention above, you may already see why they are semantically different.

    MAXIMUM_ALLOWED -- as the name suggests -- requests whatever access you have available and OBJECT_BASIC_INFORMATION::GrantedAccess can tell you what that is. If you don't get a handle and instead see STATUS_ACCESS_DENIED, you simply don't have any sort of access to said object.

    GENERIC_ALL instead requests the actual maximum access based on the generic mapping. And if you don't have all of those access types requested it will fail all the same.

    So in conclusion:

    If you wanted to be maximally permissive (the way I understand it) you'd want to use: MAXIMUM_ALLOWED, because the (still generic but more specific) GENERIC_ALL will fail to even get you a handle in many cases where MAXIMUM_ALLOWED yields one.

    In the best case -- given your intent -- you will be granted an access that amounts to GENERIC_ALL without running the risk that you don't get a handle at all.

    Alternatively you could request access to the object at your desired access level (e.g. GENERIC_ALL) and have a staggered fallback to GENERIC_WRITE etc. and as last ditch effort try MAXIMUM_ALLOWED. The advantage is that with the concrete desired access masks -- if and when you get a handle back! -- you know the access level. With MAXIMUM_ALLOWED you would still have to query OBJECT_BASIC_INFORMATION::GrantedAccess in order to find that out (well, or attempt an action and get STATUS_ACCESS_DENIED or ERROR_ACCESS_DENIED).