1

I need to find a way to guarantee that people using my library have set the requestedExecutionLevel to a minimum of highestAvailable in the application manifest, as follows:

<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
  <requestedExecutionLevel level="highestAvailable" uiAccess="false" />
</requestedPrivileges>

When they haven't set up the manifest correctly, I want to throw an exception so the developer is aware about this need, without having to read the documentation.

The main question: Is it possible to access this information, if so how?

Instead of checking this setting I already thought about checking the result of this setting. When set to highestAvailable I would expect any user part of the administrators group, to be running as administrator.

This would be possible using:

Steven Jeuris
  • 18,274
  • 9
  • 70
  • 161
  • It is XML after all, should be easy ;) – Tomas Voracek Nov 20 '13 at 15:24
  • @TomasVoracek Possibly, if you can easily find the path to the manifest file of the running application. Of course I would prefer if this information would be readily exposed somewhere in the .NET framework. – Steven Jeuris Nov 20 '13 at 15:26
  • Maybe you can make it as EmbeddedResource and read from there? Simple Assembly.GetExecutingAsembly or such should do the trick. Not sure, didn't use ClickOnce... – Tomas Voracek Nov 20 '13 at 15:28
  • @TomasVoracek I don't want to force the application developer using my library to embed or not embed the manifest. If I ask them to embed it, I can just as well state they have to set it a certain way as well. – Steven Jeuris Nov 20 '13 at 15:28
  • What does your library do? Is it possible that instead of checking for admin, you check whether a user has a certain permission? E.g. administer IIS doesn't necessary have to be done by an admin, if it's set up right. If your application administered IIS, why would you demand that a user is admin rather than "has permissions to administer IIS"? – ta.speot.is Nov 25 '13 at 11:14
  • @ta.speot.is I can check that with `WindowsPrinciple.IsInRole()` as mentioned in the question. It's an extensive window manager. The main reason I need to enforce running my app as admin when possible (hence 'highestAvailable' setting in manifest) is because otherwise it can't manage windows which run under higher privileges. – Steven Jeuris Nov 25 '13 at 11:59
  • What? Read "UIAccess for UI automation applications" http://msdn.microsoft.com/en-us/library/bb625963.aspx Half the story is: *By specifying UIAccess=”true” in the requestedPrivileges attribute, the application is stating a requirement to bypass UIPI restrictions on sending window messages across privilege levels.* – ta.speot.is Nov 25 '13 at 12:10
  • @ta.speot.is That might be relevant, but doesn't answer this question. How can I check (enforce) that the person using my library has that set? (p.s. the setting listed in the question works for me, no need to set UIaccess to true, ... therefore didn't check that far) – Steven Jeuris Nov 25 '13 at 12:17
  • If it was the answer I would have posted it as an answer. And now you're confusing "it works" with "it's correct". It's the 21st century. Jump through the hoops to get uiAccess="true" to work instead of "run as admin plz". – ta.speot.is Nov 25 '13 at 12:21
  • @ta.speot.is My bet is that it still wouldn't work since I have a process-aware window manager. I need to get information about the underlying process of the window, which is when I normally get a security exception when not running as admin and accessing an app which is running as admin. I'll definitely look into the uiAccess option as well, thanks. – Steven Jeuris Nov 25 '13 at 12:27
  • 1
    Admin might be the right level of access then, but if you want to be more specific then look at whether you have the SeDebugPrivilege http://support.microsoft.com/kb/185215 *By setting the SeDebugPrivilege privilege on the running process, you can obtain the process handle of any running application.* – ta.speot.is Nov 25 '13 at 12:31

2 Answers2

7

Your approach is flawed, the dev that uses your library is going to dislike it greatly. The issue is that the manifest isn't the only way to get a process to run elevated. And in fact is almost never the way it is done when you debug code, the Visual Studio Hosting Process is the EXE you run and has the active manifest, without "highestAvailable". The dev will instead elevate Visual Studio itself, the program he starts inherits the security token and will run elevated as well.

What you really want to know is whether the process is elevated when it uses your library and that the user account belongs to the Administrators group. That's rather hard to come by, if the process does not run elevated then the security token is one of a normal user.

You need the code available here. Throw the exception if IsUserInAdminGroup is true and IsProcessElevated is false.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Which 'flawed' approach are you referring to? Expecting the dev to put that setting to highestAvailable or the way to check for it? – Steven Jeuris Nov 25 '13 at 12:59
  • 1
    Trying to read the manifest is the flawed approach. You'll just get the one from app.vshost.exe. The wrong one since it played no role whatsoever in getting your code to run elevated. – Hans Passant Nov 25 '13 at 13:01
  • Oh I see now what you are referring to. Yes, currently when I'm using the library I need to run VS as admin as well in order to debug it. VS suggests restarting as admin actually. – Steven Jeuris Nov 25 '13 at 13:12
  • And as you stated, I'm running into the problem when the process doesn't run elevated the token reflects that of a normal user, hence the question. Funny enough, I linked to `IsUserInAdminGroup()` myself in the question. :) So the answer is there is no way to access it through .NET (`WindowsIdentity`, etc ...) and I need to use P/Invoke? While looking at that source code before I noticed it also just seems to check tokens, what is the exact difference that makes it work? – Steven Jeuris Nov 25 '13 at 13:12
  • 1
    Windows emulates a non-elevated process too well, no way to tell from .NET that the user account is in fact an admin account. Falling back to interrogating the token with pinvoke is the workaround used in the linked code. – Hans Passant Nov 25 '13 at 13:17
  • Great, that's probably the answer to the question. Would be great to update it to reflect that, since as it stands now you don't say too much more than I do in the question. Time to dive into some P/Invoke again! ;p Thanks for the help! – Steven Jeuris Nov 25 '13 at 13:21
  • Since you didn't update I added an elaborate answer myself. You can have the bounty though. ;p – Steven Jeuris Nov 26 '13 at 22:59
0

As Hans Passant points out in his answer, the real underlying question - why in this scenario you would want to check the manifest - is:

How to check whether the current user can run the process with elevated privileges?

As suggested in the question, the following would work:

var myPrincipal = new WindowsPrincipal( WindowsIdentity.GetCurrent() );
if ( !myPrincipal.IsInRole( WindowsBuiltInRole.Administrator ) &&
     IsUserInAdminGroup() )
{
    throw new NotSupportedException( "Some useful comments ..." );
}

The main question is thus, how do you write IsUserInAdminGroup()? The code listed in the UAC self-elevation sample albeit useful, doesn't explain what is going on, and why it is needed.

Hans Passant replied in a comment "Windows emulates a non-elevated process too well, no way to tell from .NET that the user account is in fact an admin account. Falling back to interrogating the token with pinvoke is the workaround used in the linked code.".

In short, you'll need to rely on P/Invoke in order to implement IsUserInAdminGroup(), of which the code can be found in the UAC sample.

More interesting perhaps, is why?

In order to find out I refactored the sample code and incorporated the function into my library. The result in my opinion is a bit more clear. Below you can find the outline, the comments are probably more relevant than the code since it depends on other classes etc ...

Starting from Windows Vista, you have different token types as expressed by TOKEN_ELEVATION_TYPE. Although you can access WindowsIdentity.Token through .NET, this isn't the token we need to check whether someone is administrator. This is a limited token. It has a linked elevated token attached to it, but this isn't exposed in .NET.

Pretty much all the (semi-pseudo) code below does is look up whether there is such an elevated token attached to the original token, and use that to check IsInRole() instead.

// Default token's received aren't impersonation tokens,
// we are looking for an impersonation token.
bool isImpersonationToken = false;

// Open the access token of the current process.
SafeTokenHandle processToken;
if ( !AdvApi32.OpenProcessToken( ..., out processToken ) )
{
    MarshalHelper.ThrowLastWin32ErrorException();
}

// Starting from Vista linked tokens are supported which need to be checked.
if ( EnvironmentHelper.VistaOrHigher )
{
    // Determine token type: limited, elevated, or default.
    SafeUnmanagedMemoryHandle elevationTypeHandle = ...;
    if ( !AdvApi32.GetTokenInformation( ... elevationTypeHandle ) )
    {
        MarshalHelper.ThrowLastWin32ErrorException();
    }
    var tokenType = (AdvApi32.TokenElevationType)Marshal.ReadInt32( 
        elevationTypeHandle.DangerousGetHandle() );

    // If limited, get the linked elevated token for further check.
    if ( tokenType == AdvApi32.TokenElevationType.TokenElevationTypeLimited )
    {
        // Get the linked token.
        SafeUnmanagedMemoryHandle linkedTokenHandle = ...;
        if ( !AdvApi32.GetTokenInformation( ... linkedTokenHandle ) )
        {
            MarshalHelper.ThrowLastWin32ErrorException();
        }
        processToken = new SafeTokenHandle(
             Marshal.ReadIntPtr( linkedTokenHandle.DangerousGetHandle() ) );
        // Linked tokens are already impersonation tokens.
        isImpersonationToken = true;
    }
}

// We need an impersonation token in order
// to check whether it contains admin SID.
if ( !isImpersonationToken )
{
    SafeTokenHandle impersonatedToken;
    if ( !AdvApi32.DuplicateToken( ..., out impersonatedToken ) )
    {
        MarshalHelper.ThrowLastWin32ErrorException();
    }
    processToken = impersonatedToken;
}

// Check if the token to be checked contains admin SID.
var identity= new WindowsIdentity( processToken.DangerousGetHandle() );
var principal = new WindowsPrincipal( identity );
return principal.IsInRole( WindowsBuiltInRole.Administrator );
Community
  • 1
  • 1
Steven Jeuris
  • 18,274
  • 9
  • 70
  • 161
  • Possibly [`WindowsIdentity.Impersonate()`](http://msdn.microsoft.com/en-us/library/w070t6ka(v=vs.110).aspx) could be used for the impersonation part? However, seems like P/Invoke has more options here as well using [`SECURITY_IMPERSONATION_LEVEL`](http://msdn.microsoft.com/en-us/library/windows/desktop/aa379572(v=vs.85).aspx). The code above uses `SecurityIdentification`. – Steven Jeuris Nov 26 '13 at 23:11