User Impersonation on Win XP SP2

V

vipleo

I am having some issues, when I try to launch another process using
Process.Start(ProcessStartInfo psi) on win xp sp2 box (Other versions
of xp have no issue).

Here is the detail.
Main app checks for updates on startup and if updates are available, it
launches separate exe to copy files.

Before launching new process(exe), I am impersonating admin user as
main app is being launched by non-admin user.

User identity is changed after impersonation, but during update,
"Access is denied" win32Exception is being thrown when main code tries
to launch copier exe.

Impersonation is implemented using following win32 api.
<code>
string domainName = string.Empty;
try
{
// Get current windows identity
string currentWindowsIdentity = WindowsIdentity.GetCurrent().Name;

domainName = currentWindowsIdentity.Substring(0,
currentWindowsIdentity.IndexOf('\\'));

const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
const int SecurityImpersonation = 2;
tokenHandle = IntPtr.Zero;
dupeTokenHandle = IntPtr.Zero;
// Call LogonUser to obtain a handle to an access token.
bool returnValue = LogonUser(_impersonationUsername,
domainName,_impersonationPassword, LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,ref tokenHandle);

if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
int errorCode = 0x5; //ERROR_ACCESS_DENIED
throw new System.ComponentModel.Win32Exception(errorCode);
}

// Check the identity.
bool retVal = DuplicateToken(tokenHandle, SecurityImpersonation, ref
dupeTokenHandle);
if (false == retVal)
{
CloseHandle(tokenHandle);
return;
}

// The token that is passed to the following constructor must
// be a primary token in order to use it for impersonation.
WindowsIdentity newId = new WindowsIdentity(dupeTokenHandle);
_impersonatedUser = newId.Impersonate();
}
catch(Exception ex)
{

}
</code>
I have enabled following security policies for non-admin user:

1) Replace a process level token.

2) Debug programs

3) Adjust memory quotas for a process

NOTE: 'Launching another exe' works fine, if no user impersonation is
used.

Any ideas, what might be wrong ?

~ViPuL
 
M

Michael.Suarez

I can't answer your question for you, but have you considered using
ClickOnce Deployment instead of your current method of checking for
updates? Then you wouldnt even need to bother with this whole issue.
 
V

vipleo

Following is the Detail Exception ia m getting:

Exception: System.ComponentModel.Win32Exception
Message: Access is denied
Source: System
at System.Diagnostics.ProcessManager.OpenProcess(Int32 processId,
Int32 access, Boolean throwIfExited)
at System.Diagnostics.NtProcessManager.GetModuleInfos(Int32
processId)
at System.Diagnostics.Process.get_Modules()
at System.Diagnostics.Process.get_MainModule()

Help!!!!!!!!!!!!!
~ViPuL
 
W

Willy Denoyette [MVP]

| Following is the Detail Exception ia m getting:
|
| Exception: System.ComponentModel.Win32Exception
| Message: Access is denied
| Source: System
| at System.Diagnostics.ProcessManager.OpenProcess(Int32 processId,
| Int32 access, Boolean throwIfExited)
| at System.Diagnostics.NtProcessManager.GetModuleInfos(Int32
| processId)
| at System.Diagnostics.Process.get_Modules()
| at System.Diagnostics.Process.get_MainModule()
|
| Help!!!!!!!!!!!!!
| ~ViPuL
|

And the code that throws this exception?

Willy.
 
V

vipleo

Here is the code I am using for launching another exe.

ProcessStartInfo proc = new ProcessStartInfo(CopierExecutable,
commandLine);
proc.WorkingDirectory = ClientAppDirectory;
proc.UseShellExecute = true; //I have also tried 'False'
Process.Start(proc);

Here, CopierExecutable represents name of exe.

~ViPuL
 
V

vipleo

Here is complete function:

private void LaunchCopierExecutable()
{
string filePath = Path.Combine(ClientAppDirectory, CopierExecutable);
if (!File.Exists(filePath))
{
throw new ApplicationException("Invalid CopierExecutable config
setting: " + CopierExecutable);
}

string commandLine = "appdir='" + ClientAppDirectory + "';upddir='" +
ClientUpdatesDirectory + "';bakdir='" + ClientBackupDirectory
+ "';procid=" + Process.GetCurrentProcess().Id + ";exe='" +
Process.GetCurrentProcess().MainModule.FileName + "';timeoutsecs=" +
MainAppShutdownSeconds.ToString() + ";";

if (_returnParms.Length > 0)
{
string sc = _returnParms.Substring(_returnParms.Length - 1, 1);
if( sc != ";") //add semi-colon
commandLine += "returnparms='" + _returnParms + "';";
else
commandLine += "returnparms='" + _returnParms + "'";
}

ProcessStartInfo proc = new ProcessStartInfo(CopierExecutable,
commandLine);
proc.WorkingDirectory = ClientAppDirectory;
proc.UseShellExecute = true;
Process.Start(proc);
}
 
W

Willy Denoyette [MVP]

That looks much better.
Your problem is that you are trying to get some information from the current
process (MainModule) while impersonating. This is not possible because your
thread runs in an impersonated context, so you don't have access to the
process context.
What you should do is get this info before impersonating.

Willy.

| Here is complete function:
|
| private void LaunchCopierExecutable()
| {
| string filePath = Path.Combine(ClientAppDirectory, CopierExecutable);
| if (!File.Exists(filePath))
| {
| throw new ApplicationException("Invalid CopierExecutable config
| setting: " + CopierExecutable);
| }
|
| string commandLine = "appdir='" + ClientAppDirectory + "';upddir='" +
| ClientUpdatesDirectory + "';bakdir='" + ClientBackupDirectory
| + "';procid=" + Process.GetCurrentProcess().Id + ";exe='" +
| Process.GetCurrentProcess().MainModule.FileName + "';timeoutsecs=" +
| MainAppShutdownSeconds.ToString() + ";";
|
| if (_returnParms.Length > 0)
| {
| string sc = _returnParms.Substring(_returnParms.Length - 1, 1);
| if( sc != ";") //add semi-colon
| commandLine += "returnparms='" + _returnParms + "';";
| else
| commandLine += "returnparms='" + _returnParms + "'";
| }
|
| ProcessStartInfo proc = new ProcessStartInfo(CopierExecutable,
| commandLine);
| proc.WorkingDirectory = ClientAppDirectory;
| proc.UseShellExecute = true;
| Process.Start(proc);
| }
|
 
V

vipleo

Thanks for reply, willy.

But why this would be problem on win xp sp2 only.

Thanks,
~ViPuL
 
W

Willy Denoyette [MVP]

| Thanks for reply, willy.
|
| But why this would be problem on win xp sp2 only.
|
| Thanks,
| ~ViPuL
|

Different user privileges? Changes in SP2?

A process con only be 'opened' by the owner or by a caller with debug
provileges.
What I saw in your stack dump was that the caller did not have the requied
provileges to open the process:
<
Message: Access is denied
Source: System
at System.Diagnostics.ProcessManager.OpenProcess(Int32 processId,
Int32 access, Boolean throwIfExited)now I know why, the impersonating user is not the "owner" and he does not
have the required access privileges.

Willy.
 
V

vipleo

Willy,
As I had mentioned in my first post, I am impersonating as ADMIN user
who has explicit debug privileges.

Main App is launched by regular user and "admin" user is being
impersonated before app tries to launch another exe.

anyway, I will try your suggestion.

Thanks for your help.
~ViPuL
 
W

Willy Denoyette [MVP]

| Willy,
| As I had mentioned in my first post, I am impersonating as ADMIN user
| who has explicit debug privileges.
|
| Main App is launched by regular user and "admin" user is being
| impersonated before app tries to launch another exe.
|
| anyway, I will try your suggestion.
|
| Thanks for your help.
| ~ViPuL
|

Impersonating is not sufficient, you also need to "enable" the privilege,
it's not because a user has debug privileges that they are enabled.

Willy.
 
J

JaredHite1

vipleo-
I feel your pain...I spent weeks on this very issue - trying to get an
application updater going when the users didn't have permission to
install it. Our old impersonation broke when users started upgrading
to SP2, so I had to redo it.

It has been over a year, and there were so many issues they all kind of
blend together, but as I recall, impersonating then starting the
install process did not do the job because impersonation only changes
the permissions of the thread, not the process, and the process.start
gets started under the process' permissions. Again, it's a bit fuzzy
so I may be completely off there. I remember testing it by just
impersonating a different user than myself to start a process, then
that process popped a message box indicating what the current user was.

Also noteworthy was that if you had v 2.0, the Process.Start now takes
username, password, and domain parameters - obviously MS noticed this
was sorely missed.

Anyway, the silver bullet was to use WMI to install the new version -
that way the installation could happen on the current thread and the
admin user's privileges were used appropriately. It's been tried and
tested with 2000 users every two weeks, with no problems. I've seen
examples of using WMI to run the process as well, but I never got them
to work for me - I don't remember why.

If this is an option for you let me know and I'll post some code.

Good luck,
Jared
 
V

vipleo

Jared,
Thanks a lot for your help & feeling my pain. <g>

In our implementation, we are not using multiple thread to do app
auto-update.

We are doing auto-update(xCopy deployment) in same thread, so I don't
understand how user context could be different.

Btwn, if you(or anybody else) can post some code sample for 'how to use
wmi to auto-update app' or 'how to launch another process using wmi',
that would be really great!

Thanks,
~ViPuL
 
J

JaredHite1

We weren't using multi-threading either, but your code still runs on a
thread (even if it's the only one in the process), so it could still be
causing a problem. Read the "Pitfalls to watch for" section at
http://pluralsight.com/wiki/default.aspx/Keith.GuideBook.WhatIsImpersonation.
Note that I'm fairly certain that the suggestion to use
createprocessasuser no longer works on SP2 - that's what caused me to
have to do a rewrite in the first place. This is documented on MSDN,
if you care to search for it.

OK...Here's the code that I'm 100% sure works. I've stripped out a lot
of the stuff that was implementation specific, but I think all the
pertinent parts are in here:

Impersonation class: This one does the actual installing and
uninstalling.
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Windows.Forms;
using System.Management;
using System.Collections;
using ROOT.CIMV2.Win32;


namespace Extensions.Client.ApplicationUpdater
{
public class Impersonate
{
[DllImport("advapi32.dll", SetLastError=true)]
private static extern bool LogonUser(String lpszUsername, String
lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

public static void UninstallProduct(string productCode, string name,
string version)
{
//get error codes at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/error_codes.asp
const int ERROR_SUCCESS_REBOOT_REQUIRED = 3010;
const int ERROR_SUCCESS_REBOOT_INITIATED = 1641;
const int ERROR_SUCCESS = 0;

uint retValue = 0;
try
{
ManagementScope scope = new ManagementScope();
ROOT.CIMV2.Win32.Product prod = new Product(productCode, name,
version);
prod.Scope = scope;
retValue = prod.Uninstall();
}
catch(Exception e)
{
MessageBox.Show("Uninstall failed. Verify that " + name + " is
installed for all users on this machine. Details: " + e.Message);
}
if (retValue != ERROR_SUCCESS_REBOOT_REQUIRED && retValue !=
ERROR_SUCCESS_REBOOT_INITIATED && retValue != ERROR_SUCCESS)
{
MessageBox.Show("Uninstall failed. Please contact helpdesk to get
updates. The returned error was: " + retValue);
}
}

public static void InstallProduct(string path, string options, bool
allUsers)
{
//get error codes at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/error_codes.asp
uint retValue = 0;
const int ERROR_SUCCESS_REBOOT_REQUIRED = 3010;
const int ERROR_SUCCESS_REBOOT_INITIATED = 1641;
const int ERROR_SUCCESS = 0;

try
{
if (System.IO.File.Exists(path))
{
retValue = Product.Install(allUsers, options, path);
if (retValue != ERROR_SUCCESS_REBOOT_REQUIRED && retValue !=
ERROR_SUCCESS_REBOOT_INITIATED && retValue != ERROR_SUCCESS)
{
MessageBox.Show("Install failed. Please contact helpdesk to get
updates. The returned error was: " + retValue);
}
}
else
MessageBox.Show("Couldn't find file " + path + ". Please contact
helpdesk to get updates.");
}
catch(Exception e)
{
MessageBox.Show("Install failed. Please contact helpdesk to get
updates. Details: " + e.Message);
}
}

public static IntPtr LogonUser(string UserName, string Password,
string Domain)
{
IntPtr token = IntPtr.Zero;
try
{
// Call LogonUser to obtain a handle to an access token.
LogonUser(UserName, Domain, Password, 2, 1, ref token);
return token;
}
catch
{
return IntPtr.Zero;
}
}
}
}

And here how the impersonation class was used to actually install the
product:
private void Install()
{

try
{
if (!System.IO.File.Exists(this.msiPath))
{
MessageBox.Show("Could not find the installation file " +
this.msiPath + ". Please contact the helpdesk to get updates.", "New
version of " + this.applicationTitle + " not found.");
return;
}
StatusWindow statusForm = null;

statusForm = new StatusWindow();
statusForm.Status = "Preparing to install " + this.applicationTitle +
"...";
statusForm.Show();

statusForm.Status = "Logging On...";
statusForm.PerformStep();

IntPtr token = Impersonate.LogonUser(UserName, Password, Domain);

if (token != IntPtr.Zero)
{
WindowsIdentity newId = new WindowsIdentity(token);
WindowsImpersonationContext impersonatedUser = newId.Impersonate();
//EVERTHING YOU DO FROM HERE ON OUT IS IMPERSONATED

//You probably can ignore all this productCode stuff...It's a long
story. But if your MSI is set to remove previous versions, you don't
need this.
if (this.productCode != null && this.productCode.Length > 0)
{
statusForm.Status = "Uninstalling old version of " +
this.applicationTitle + " ...";
statusForm.PerformStep();
Impersonate.UninstallProduct(this.productCode,
this.applicationTitle, this.oldVersion);
}

statusForm.Status = "Installing new version of " +
this.applicationTitle + "...";
statusForm.PerformStep();

//The second parameter are the options sent to msiexec.
Impersonate.InstallProduct(this.msiPath, "REBOOT=R", true);

impersonatedUser.Undo();
//BACK TO RUNNING UNDER THE ORIGINAL USER
}
else
{
MessageBox.Show("Impersonate user failed. Please contact HelpDesk
to obtain the latest version.");
}

statusForm.Close();
}
catch(Exception e)
{
MessageBox.Show(this.applicationTitle + " update failed: " + e);
WriteEvent(e);
}
}

I'll look around for the example of doing it with a process.

Good luck,
Jared
 
J

JaredHite1

Here's the example that served as my bible for much of the time I spent
working on this issue:
http://www.dotnet247.com/247reference/msgs/55/275561.aspx

Even though WMI may not be your solution, the concept may still work
for you. Instead of using the Process.Start, you need to duplicate the
action in-process (or, more accurately, in-thread.) So if you were
xcopying the files over, I believe that just using the
System.IO.File.Copy method will accomplish the same thing, while
allowing you to do it under the impersonated user's credentials.

Good luck,
Jared
 
W

Willy Denoyette [MVP]

| Willy,
| How i can 'enable' this privilege?
|

You have to call AdjustTokenPrivileges through PInvoke to do this from C#,
but you also need to get at the Process token because this fnction needs
this token, and you need to do this before impersonating, because once you
are impersonating you can't open the process to get at the token, see what I
mean? You can do all this using PInvoke, but it's not trivial, you better do
it in C++, or just don't do it at all and do as I said, get the module name
before you impersonate.

Willy.
 
W

Willy Denoyette [MVP]

As I said to vipleo, impersonation isn't the issue here, and is probably not
related to your problem. The OP's problem is that he tries to open the
'current process' while impersonating, this isn't allowed, unless the
impersonating user has Debug Privileges (he isn't the owner of the process)
and that these are explicitely enabled.


Willy.



| vipleo-
| I feel your pain...I spent weeks on this very issue - trying to get an
| application updater going when the users didn't have permission to
| install it. Our old impersonation broke when users started upgrading
| to SP2, so I had to redo it.
|
| It has been over a year, and there were so many issues they all kind of
| blend together, but as I recall, impersonating then starting the
| install process did not do the job because impersonation only changes
| the permissions of the thread, not the process, and the process.start
| gets started under the process' permissions. Again, it's a bit fuzzy
| so I may be completely off there. I remember testing it by just
| impersonating a different user than myself to start a process, then
| that process popped a message box indicating what the current user was.
|
| Also noteworthy was that if you had v 2.0, the Process.Start now takes
| username, password, and domain parameters - obviously MS noticed this
| was sorely missed.
|
| Anyway, the silver bullet was to use WMI to install the new version -
| that way the installation could happen on the current thread and the
| admin user's privileges were used appropriately. It's been tried and
| tested with 2000 users every two weeks, with no problems. I've seen
| examples of using WMI to run the process as well, but I never got them
| to work for me - I don't remember why.
|
| If this is an option for you let me know and I'll post some code.
|
| Good luck,
| Jared
|
 
W

Willy Denoyette [MVP]

| Jared,
| Thanks a lot for your help & feeling my pain. <g>
|
| In our implementation, we are not using multiple thread to do app
| auto-update.
|
| We are doing auto-update(xCopy deployment) in same thread, so I don't
| understand how user context could be different.
|
| Btwn, if you(or anybody else) can post some code sample for 'how to use
| wmi to auto-update app' or 'how to launch another process using wmi',
| that would be really great!
|
| Thanks,
| ~ViPuL
|

I don't think this will help you anyway, what you are after is the filename
of the current executable, right? Well, you should get this one before
impersonating and you are done.
Also, you shouldn't even use Process.GetCurrentProcess().MainModule.FileName
to get the exe name of the current process, you simply have to get the name
of the default application domain, this one is by default the same as the
..exe assembly name.

Willy.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top