Bugs in System.Diagnostics.Process Class

While working on an application which makes use of Process class I found several bugs which occur in special conditions. More specifically accessing StartTime or HasExited properties causes a Win32Exception with NativeErrorCode equal to five. This indicates that the exception is caused by not having enough rights to retrieve the required information.

The exception occurs under the following conditions:

  • The source application should not be elevated
    • The process instance should represent an elevated process
    • The instance should not be a result of Process.Start call

The snippet below demonstrates these points:

//The following code will not throw an exception unless explorer is running elevated
var explorer = Process.GetProcessesByName("explorer").First();
Console.WriteLine(explorer.StartTime);
Console.WriteLine(explorer.HasExited);

var startInfo = new ProcessStartInfo("notepad") { Verb = "runas" };
var notepad = Process.Start(startInfo);

//Even though notepad is an elevated process no exception will be thrown
Console.WriteLine(notepad.StartTime);
Console.WriteLine(notepad.HasExited);

var notepad2 = Process.GetProcessById(notepad.Id);

//Exception is thrown.
try
{
    Console.WriteLine(notepad2.StartTime);
}
catch(Win32Exception e)
{
    Console.WriteLine(e.ToString());
}

try
{
    Console.WriteLine(notepad2.HasExited);
}
catch(Win32Exception e)
{
    Console.WriteLine(e.ToString());
}

By looking at the stacktrace we get when accessing StartTime property we can see that Process.StartTime calls Process.GetProcessTimes which calls Process.GetProcessHandle which in turn calls ProcessManager.OpenProcess. This is where the exception is occurring. Using Reflector we can see that ProcessManager.OpenProcess invokes native function OpenProcess and passes the desired access. In our case the access that is passed is equal to 0x400 which corresponds to PROCESS_QUERY_INFORMATION access right. However, starting from Vista there is a new access right called PROCESS_QUERY_LIMITED_INFORMATION which is enough for calling GetProcessTimes function. Let’s see what we get when we call GetProcessTimes with the new access right:

var startinfo = new ProcessStartInfo("notepad") { Verb = "runas" };
var process = Process.Start(startinfo);
var process2 = Process.GetProcessById(process.Id);

long create, exit, kernel, user;
var handle = NativeMethods.OpenProcess(0x1000, false, process2.Id);
NativeMethods.GetProcessTimes(handle, out create, out exit, out kernel, out user);
NativeMethods.CloseHandle(handle);
Console.WriteLine(DateTime.FromFileTime(create));

If you run this snippet you will see that process start time is printed and no exception occurs. This solves the problem with StartTime property.

Printing the stacktrace of HasExited property shows that the exception occurs at exactly the same place. In this case the requested access is 0x100400 which is combination of PROCESS_QUERY_INFORMATION and SYNCHRONIZE. As you have probably guessed it is again enough to use PROCESS_QUERY_LIMITED_INFORMATION instead of PROCESS_QUERY_INFORMATION. I will not show the full code here as it is quite straightforward.

So, if you receive access denied exception when querying StartTime or HasExited make sure that the target process is not an elevated process. If it is you will have to manually retrieve the information you need.

Avatar
Giorgi Dalakishvili
World-Class Software Engineer