Today we deployed our newly created ASP.NET application to the server and soon we realized there was a strange security-related issue which was causing the application to crash. This is an internal application and we use Impersonation to manage how the users access resources. The application however, throws an "Access Denied" exception when the user attemps to access a folder over which they have full control.
The exception was in fact an AggregateException
and was being thrown in a method which uses Parallel.ForEach
to enumerate over a list and inside the body, it attempts to access the folder, but at this point the Impersonation Context gets changed and the worker thread runs as the application pool's identity, which doesn't have access to the folder hence the exception.
To confirm this, I looked at the process identity before and inside the body of Parallel.ForEach
:
string before = WindowsIdentity.GetCurrent().Name;
Debug.WriteLine("Before Loop: {0}", before);
Parallel.ForEach(myList, currentItem =>
{
string inside = WindowsIdentity.GetCurrent().Name;
Debug.WriteLine("Inside Loop: {0} (Worker Thread {1})", inside, Thread.CurrentThread.ManagedThreadId);
});
When I run the app, this is what gets printed out:
Before Loop: MyDomain\ImpersonatedUser
Inside Loop: NT AUTHORITY\SYSTEM (Worker Thread 8)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 6)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 7)
Inside Loop: NT AUTHORITY\SYSTEM (Worker Thread 9)
Inside Loop: NT AUTHORITY\SYSTEM (Worker Thread 10)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 7)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 6)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 7)
As you can see, some threads are running as the impersonated identity and some as the application pool (in this case, LocalSystem
) and there doesn't seem to be a pattern. The previous frame in the Call Stack
window also goes to the unmanaged kernel32.dll
, which makes me think CLR isn't validating the context before delegating it to the OS.
Any idea why this is happening? Is that a known issue/bug?
The Parallel. ForEach method splits the work to be done into multiple tasks, one for each item in the collection. Parallel. ForEach is like the foreach loop in C#, except the foreach loop runs on a single thread and processing take place sequentially, while the Parallel.
No, it doesn't block and returns control immediately. The items to run in parallel are done on background threads.
In many cases, Parallel. For and Parallel. ForEach can provide significant performance improvements over ordinary sequential loops. However, the work of parallelizing the loop introduces complexity that can lead to problems that, in sequential code, are not as common or are not encountered at all.
The execution of Parallel. Foreach is faster than normal ForEach.
Unlike the Task
class, Parallel
doesn't seem to be capturing the ExecutionContext
you're currently running on (which in turns captures the SecurityContext
which holds the WindowsIdentity
). It uses the one avaliable inside the current Thread.
You have to explicitly capture the desired context:
IntPtr token = WindowsIdentity.GetCurrent().Token;
Parallel.ForEach(myList, currentItem =>
{
using (WindowsIdentity.Impersonate(token))
{
string inside = WindowsIdentity.GetCurrent().Name;
Debug.WriteLine("Inside Loop: {0} (Worker Thread {1})", inside, Thread.CurrentThread.ManagedThreadId);
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With