Parallel.ForEach()更改模拟上下文

本文关键字:模拟 上下文 ForEach Parallel | 更新日期: 2023-09-27 18:24:53

今天我们将新创建的ASP.NET应用程序部署到服务器上,很快我们就意识到存在一个奇怪的安全相关问题,导致应用程序崩溃。这是一个内部应用程序,我们使用模拟来管理用户访问资源的方式。然而,当用户试图访问他们完全控制的文件夹时,应用程序会抛出"拒绝访问"异常。

该异常实际上是一个AggregateException,并且是在一个方法中抛出的,该方法使用Parallel.ForEach在列表上枚举,在主体内部,它试图访问文件夹,但此时模拟上下文发生了更改,工作线程作为应用程序池的标识运行,而应用程序池无权访问文件夹,因此出现了异常。

为了证实这一点,我查看了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);
});

当我运行应用程序时,会打印出以下内容:

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)

正如您所看到的,一些线程以模拟身份运行,另一些线程以应用程序池(在本例中为LocalSystem)运行,但似乎没有模式。Call Stack窗口中的前一帧也转到非托管的kernel32.dll,这让我认为CLR在将上下文委派给操作系统之前没有验证上下文。

知道为什么会发生这种事吗?这是已知的问题/错误吗?

Parallel.ForEach()更改模拟上下文

Task类不同,Parallel似乎没有捕获您当前运行的ExecutionContext(它依次捕获包含WindowsIdentitySecurityContext)。它使用当前线程中可用的线程。

您必须明确地捕获所需的上下文:

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);
   }
});

这里有一个C#扩展方法,可以让这变得更容易。。。

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);
            });
        }

以下是您对它的称呼:

puppies.ParallelForEach(
                    CurrentUser.Instance.Principal,
                    new ParallelOptions { MaxDegreeOfParallelism = 8 },
                    (puppy) => PetAnimal(puppy)
                );

Windows中的整个模拟概念是一个每线程的概念。您可以看到,当创建一个新线程时,它会继承进程的特权令牌。但是线程,进程不同,也有一个模拟令牌。调用WindowsIdentity.Impersonate(令牌)时,将在调用线程上设置模拟令牌。

此外,当您调用Impersonate时,您将线程的主令牌指针设置为指向模拟令牌,而不是进程的主令牌,这是默认值。

在WinAPI中多一点理解和知识,就会让您知道在服务中发生的事情是预期的行为。