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在将上下文委派给操作系统之前没有验证上下文。
知道为什么会发生这种事吗?这是已知的问题/错误吗?
与Task
类不同,Parallel
似乎没有捕获您当前运行的ExecutionContext
(它依次捕获包含WindowsIdentity
的SecurityContext
)。它使用当前线程中可用的线程。
您必须明确地捕获所需的上下文:
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中多一点理解和知识,就会让您知道在服务中发生的事情是预期的行为。