Correct GENERIC_MAPPING for process and thread objects in AccessCheck

yohei watanabe 0 Reputation points
2026-09-08T00:10:38.1266667+00:00
I am designing a native x64 C diagnostic tool and would like to clarify the supported use of GENERIC_MAPPING with AccessCheck for process and thread objects.

Planned environment:
- Windows 11 Pro 25H2 x64, build 26200.9168
- Windows SDK 10.0.26100.0

This is an API contract question. I am not reporting test results from that Windows build.

The tool will copy security descriptors obtained from retained process and thread handles, then call AccessCheck using the observer's own impersonation token.

The goal is to determine whether the granted rights are a subset of a predefined allowed mask. These copied-descriptor calculations will be recorded separately from actual OpenProcess and OpenThread access attempts.

The planned inputs are:
- Fixed specific and standard access rights.
- Separate requests using MAXIMUM_ALLOWED.
- Input validation that rejects generic access bits in DesiredAccess and in every ACE access mask before calling AccessCheck.

I have reviewed the documentation for AccessCheck, GENERIC_MAPPING, Process Security and Access Rights, and Thread Security and Access Rights. However, I have not located a documented table specifying all four GENERIC_MAPPING values for these two object types.

Could you clarify the following?

1. What are the correct GenericRead, GenericWrite, GenericExecute, and GenericAll values for process objects and thread objects? Please include the applicable Windows versions or build conditions, and a Microsoft documentation reference where available.

2. If applications should not hard-code these values, is there a supported user-mode method for obtaining the mappings? Please identify the API, information class, documented output structure, and any version or access-right requirements.

3. For the restricted input described above, where neither DesiredAccess nor any ACE mask contains generic access bits, what GENERIC_MAPPING input is supported when using MAXIMUM_ALLOWED? Alternatively, is there another supported way to evaluate whether a token's granted rights on a copied process or thread security descriptor are a subset of an allowed mask?

I am not assuming that a zero-initialized mapping, ALL_ACCESS in every field, or a mapping for a different object type is valid. I am also not relying on a private interpretation of the reserved fields returned by NtQueryObject.

I would appreciate either documented values with their applicability, or guidance on a supported alternative. I am not requesting confidential implementation details or a guarantee that the same values apply to every Windows version.

Thank you.
Windows development | Windows API - Win32
0 comments No comments

1 answer

Sort by: Most helpful
  1. Taki Ly (WICLOUD CORPORATION) 4,440 Reputation points Microsoft External Staff Moderator
    2026-09-08T04:01:19.68+00:00

    Hello @yohei watanabe ,

    To your first question, I don't think you missed anything. Windows simply doesn't publish a GENERIC_MAPPING table for process and thread objects. As far as I know, the mapping is defined inside the kernel (on PsProcessType and PsThreadType), it isn't part of the public Win32 contract, and it isn't guaranteed to stay constant across versions. That's why the Process and Thread security pages only list the specific and standard rights along with *_ALL_ACCESS. So, I'd advise against hard-coding the four values, since there's no stable contract to rely on.

    On your second question about a dynamic method, I'm not aware of a fully documented, supported way to read the mapping from user mode. The only call I know of that returns it is NtQueryObject with ObjectTypeInformation, where the mapping shows up in OBJECT_TYPE_INFORMATION.GenericMapping. But that field sits in the reserved, subject-to-change part of winternl.h, the same area you've already decided not to depend on so I wouldn't treat it as a supported contract either.

    I think your third question is where the problem actually resolves. GENERIC_MAPPING is only used to translate generic bits, and since you already validate and reject generic bits in both DesiredAccess and every ACE mask before calling AccessCheck, no translation happens so whatever you pass in that structure can't change the result. You just need a valid, non-NULL pointer; setting all four fields to the object's *_ALL_ACCESS is fine. The same holds with MAXIMUM_ALLOWED: it wants a valid mapping pointer, but the contents don't matter once the descriptor has no generic bits.

    If what you're really after is checking whether the granted rights are a subset of an allowed mask, I'd suggest avoiding the mapping altogether. Let AccessCheck compute GrantedAccess under MAXIMUM_ALLOWED, then compare with bit math:

    // AccessCheck(..., MAXIMUM_ALLOWED, &genericMapping, ..., &granted, &status);
    BOOL isSubset = ((granted & ~AllowedMask) == 0);
    

    As long as you express AllowedMask in specific and standard bits, this is fully supported and doesn't depend on any version-specific mapping. So, my suggestion is to keep rejecting generic bits at your input boundary, pass a valid placeholder mapping, and run the subset test on GrantedAccess. That keeps everything on documented behavior.

    Hope this helps. If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation.

    Thank you.

    Was this answer helpful?


Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.