【问题标题】:Parallel.ForEach() changes Impersonation ContextParallel.ForEach() 更改模拟上下文
【发布时间】:2014-11-21 19:04:58
【问题描述】:

今天我们将新创建的 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 在将上下文委托给操作系统之前没有对其进行验证。

知道为什么会这样吗?这是一个已知问题/错误吗?

【问题讨论】:

    标签: c# .net task-parallel-library impersonation parallel.foreach


    【解决方案1】:

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

    【讨论】:

    • 但为什么它确实似乎不是一种模式?
    • 我不太确定。我以前见过有关这种方式的问题。似乎是一个应该实现的功能,因为它是预期的行为。
    • 是的,我使用WindowsIdentity.GetCurrent().Impersonate() 作为解决方法,但我仍然不明白为什么线程之间存在差异。如果SecurityContext 没有被捕获,为什么有些线程仍然以模拟身份运行?我觉得还有更多。
    • 是的,我也想知道:上下文要么总是被捕获,要么并不总是被捕获。但实际上是有时
    • 我认为Parallel 也使用当前正在执行的线程作为其线程池的一部分。这就是为什么我们有时会看到正确的身份。
    【解决方案2】:

    这是一个 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)
                    );
    

    【讨论】:

      【解决方案3】:

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

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

      多一点对 WinAPI 的理解和了解会让您知道您的服务中发生的事情预期的行为。

      【讨论】:

      • 少一点小蛇会让世界变得更美好,这应该是预期的行为。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-04
      • 2017-04-01
      • 2013-08-01
      • 1970-01-01
      • 2011-01-07
      • 2020-10-23
      • 2019-07-10
      相关资源
      最近更新 更多