October 17th, 2022

How can I check the integrity level of my process?

Integrity levels capture the sense of “running as a regular Win32 process”, “running elevated”, “running in a sandbox process”, that sort of thing. They describe what degree of security enforcement is applied to the process and how protected the process is from other processes.

You can inspect a process’s integrity level by calling Get­Token­Information and asking for Token­Integrity­Level. For this trick, I’m going to use the magic Get­Current­Process­Token() function to save me the trouble of hunting down the process token (and then closing it when done). And I’ll use the wil::get_token_information helper function from the Windows Implementation Library to do the grunt work of calling Get­Token­Information twice, once to get the buffer size, and again to fill it.

#include <wil/token_helpers.h>

DWORD GetCurrentProcessIntegrityLevel()
{
  auto info = wil::get_token_information<TokenIntegrityLevel>(
    GetCurrentProcessToken());
  auto sid = info->Label.Sid;
  return *GetSidSubAuthority(sid, 
    *GetSidSubAuthorityCount(sid)-1);
}

To get the integrity level, we obtain the Token­Integrity­Level information, which takes the form of a TOKEN_MANDATORY_LABEL. That structure consists of a Label, and in the Label is a Sid. That’s where the integrity level is.

The integrity level is encoded in the SID as the relative identifier (the final subauthority). So we ask how many subauthorities there are and ask for the last one.

All that’s left is mapping that integer to a semantic range.

auto integrityLevel = GetCurrentProcessIntegrityLevel();
if (integrityLevel >= SECURITY_MANDATORY_SYSTEM_RID) {
  print("System integrity");
} else if (integrityLevel >= SECURITY_MANDATORY_HIGH_RID) {
  print("High integrity");
} else if (integrityLevel >= SECURITY_MANDATORY_MEDIUM_RID) {
  print("Medium integrity");
} else if (integrityLevel >= SECURITY_MANDATORY_LOW_RID) {
  print("Low integrity");
} else {
  print("Below low integrity?");
}

Alternatively, we can check from low to high, but the tests look weird because we’re testing the upper boundary of the range, which is named after the next range.

auto integrityLevel = GetCurrentProcessIntegrityLevel();
if (integrityLevel < SECURITY_MANDATORY_LOW_RID) {
  print("Below low integrity?");
} else if (integrityLevel < SECURITY_MANDATORY_MEDIUM_RID) {
  print("Low integrity");
} else if (integrityLevel < SECURITY_MANDATORY_HIGH_RID) {
  print("Medium integrity");
} else if (integrityLevel < SECURITY_MANDATORY_SYSTEM_RID) {
  print("High integrity");
} else {
  print("System integrity");
}

Note the importance of using range checks rather than direct equality checks. That way, you will successfully handle new integrity levels that are created inside an existing range, such as SECURITY_MANDATORY_MEDIUM_PLUS_RID, which is an integrity level inserted into the “Medium” range, above the regular SECURITY_MANDATORY_MEDIUM_RID. There’s also an unnamed integrity level that is SECURITY_MANDATORY_MEDIUM_RID + 0x10 which is assigned to medium integrity applications with UIAccess rights.

The sample code in the archived content almost gets it right, but it forgets to handle the case of an integrity level less than SECURITY_MANDATORY_LOW_RID.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

11 comments

Discussion is closed. Login to edit/delete existing comments.

  • schbaem

    Using auto in blog posts to declare variables is a horrible thing, because without intellisense there is just no help to identify what is going on.

    • Jeff Howe

      I used Intuitionsense(TM) to look up TokenIntegrityLevel, found that it’s a member of the TOKEN_INFORMATION_CLASS enumeration, used in something called GetTokenInformation() to retrieve a TOKEN_MANDATORY_LABEL structure. Intellisense isn’t going to tell you anything more than ‘info’ is a pointer to a TOKEN_MANDATORY_LABEL, like what it is and what you can do with it, whereas IntuitionSense can do that.

      • Sigge Mannen

        Well, auto does lose in Google hits. Unless of course, they have also implemented Intuitionsense(TM)(R)(C)

  • skSdnW

    Which begs the question, why does the TokenElevation information class exist and why does it produce the wrong result (at least it did up to including 8.1, not tested this in a while).

    This can lead to a confusing situation:

    1 Elevate cmd.exe.
    2 Craft a program that takes this elevated token and reduces the IL to Low IL and start another cmd.exe with this new token.
    3 The new Low IL cmd.exe in a new console...

    Read more
    • Harry Johnston · Edited

      I believe the elevation type is set during logon, i.e., TokenElevationTypeDefault if no split token is created, or TokenElevationTypeFull and TokenElevationTypeLimited for the elevated and non-elevated split tokens, respectively.

      Presumably if you are modifying a token by lowering the integrity level it is your responsibility to disable the administrators group and change the elevation type accordingly. Correction: it doesn't seem to be possible to change the elevation type of an existing token.

      (I would note that...

      Read more
      • skSdnW

        I’m talking about TOKEN_ELEVATION, not TOKEN_ELEVATION_TYPE.

        Regardless, the net result is that the console is marked as administrator even though the token can’t pass any actual security checks because those actually check the IL instead of whatever TOKEN_ELEVATION is supposed to be used for.

      • Harry Johnston

        Yes, but this is because you are modifying the token in a way you shouldn't.

        I've done a bit of digging but there doesn't seem to be any way to change an elevated token into a non-elevated token. Even the LUA_TOKEN option to CreateRestrictedToken doesn't work. I did find this a little surprising. In practice, though, it doesn't matter, because the correct solution is to use the limited token that was generated for...

        Read more
      • Harry Johnston

        I've just checked, and if you run Edge elevated it first de-elevates using the limited token generated at logon, and only then starts implementing the further restrictions you're talking about. (You can confirm this with Process Explorer by checking the logon session; at logon, the elevated and non-elevated tokens get assigned to different logon sessions. All the Edge processes I can find are in the non-elevated logon session, even though I launched Edge...

        Read more
      • MGetz

        Observation: That may not be safe to do so, If you're deliberately lowering a token you probably want to be extremely explicit with it because you have extremely good security reasons for not running elevated. For example Chromium (and thus Edge) start browser processes with the untrusted SID and token level and then carefully build up a token that gives them just enough to run. You can actually see each process' current sandbox state using...

        Read more
  • MGetz

    There have been times in my code I've been sorely tempted to add checks like this to make sure someone isn't running my app elevated. I've never actually gone forwards with it because there is always that one user that turns off UAC completely and then complains about everything. That said I do my best to ensure everything works perfectly at medium integrity. I'd do low but that would be too much of a pain...

    Read more
    • Harry Johnston · Edited

      If I remember correctly, it is possible to distinguish between your process being elevated and UAC being turned off. I think TokenElevationType is the appropriate query: TokenElevationTypeDefault means UAC is off or the user isn't an administrator, TokenElevationTypeFull means the process is elevated, and TokenElevationTypeLimited means the process is not elevated. I haven't tested this recently, so caveat emptor.

      (On general principles though, and without knowing what sort of applications you're talking about, if...

      Read more