Parallel.ForEach() changes Impersonation Context
Asked Answered
P

3

10

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?

Potted answered 26/9, 2014 at 17:29 Comment(0)
C
4

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);
   }
});
Carolinecarolingian answered 26/9, 2014 at 17:40 Comment(5)
But why it indeed doesn't seem to be a pattern?Gyronny
Im not too sure. I've previously seen questions regarding this manner. Seems like a feature that should be implemented as it is the expected behavior.Carolinecarolingian
Yes I'm using WindowsIdentity.GetCurrent().Impersonate() as a workaround but I still don't understand why there's a discrepancy among threads. If the SecurityContext isn't being captured, how come some of the threads still run as the impersonated identity? I feel like there's more to this.Potted
Yes, I'm wondering the same: context is either captured always or it isn't always. But actually it's sometimes.Gyronny
I think Parallel also uses the current executing thread as part of its thread pool. Thats why we sometimes see the right identity.Carolinecarolingian
A
0

Here is a C# extension method to make this a little easier...

public static ParallelLoopResult ParallelForEach<TSource>(this IEnumerable<TSource> source, IPrincipal principal, ParallelOptions parallelOptions, Action<TSource> body)
        {
            return Parallel.ForEach(source, parallelOptions, (source) =>
            {
                if (!CurrentUser.Instance.IsAuthenticated)
                    Thread.CurrentPrincipal = principal;
                body(source);
            });
        }

And here is how you call it:

puppies.ParallelForEach(
                    CurrentUser.Instance.Principal,
                    new ParallelOptions { MaxDegreeOfParallelism = 8 },
                    (puppy) => PetAnimal(puppy)
                );
Archaism answered 2/3, 2021 at 18:39 Comment(0)
H
-3

The whole Impersonation concept in Windows is a per thread concept. You see, when a new thread is created, it inherits the process' privileges token. But threads, unlike processes, also have an impersonation token. When you call WindowsIdentity.Impersonate(token) you set the impersonation token on the calling thread.

Also when you call Impersonate you set the thread's primary token pointer to point to the impersonation token instead of the process' primary token, which is the default.

A little bit more understanding and knowledge in the WinAPI would lead you to know that what happened in your service is the expected behavior.

Hexangular answered 2/11, 2014 at 18:37 Comment(1)
And a little less snark makes the world a better place, and should be the expected behaviour.Resignation

© 2022 - 2024 — McMap. All rights reserved.