gpt4 book ai didi

.net - 寻找一种实用的沙盒 .NET 插件方法

转载 作者:行者123 更新时间:2023-12-03 05:44:36 25 4
gpt4 key购买 nike

我正在寻找一种简单而安全的方法来从 .NET 应用程序访问插件。尽管我认为这是一个非常常见的要求,但我正在努力寻找满足我所有需求的任何东西:

  • 主机应用程序将在运行时发现并加载其插件程序集
  • 插件将由未知的第三方创建,因此必须将它们放入沙箱中以防止它们执行恶意代码
  • 通用互操作程序集将包含主机及其插件引用的类型
  • 每个插件程序集将包含一个或多个实现通用插件接口(interface)的类
  • 初始化插件实例时,主机将以主机接口(interface)的形式向其传递对自身的引用
  • 主机将通过其公共(public)接口(interface)调用插件,插件也可以同样调用主机
  • 主机和插件将以互操作程序集中定义的类型(包括泛型类型)的形式交换数据

我已经研究了 MEF 和 MAF,但我正在努力了解如何使它们中的任何一个能够满足要求。

假设我的理解是正确的,MAF 无法支持跨其隔离边界传递泛型类型,这对我的应用程序至关重要。 (MAF 的实现也非常复杂,但如果我能解决泛型类型问题,我就准备好使用它)。

MEF 几乎是一个完美的解决方案,但似乎达不到安全要求,因为它在与主机相同的 AppDomain 中加载其扩展程序集,因此显然可以防止沙箱。

我见过this question ,其中谈到在沙盒模式下运行 MEF,但没有描述如何运行。 This post声明“使用 MEF 时,您必须信任扩展不会运行恶意代码,或通过代码访问安全性提供保护”,但它同样没有描述如何操作。最后还有this post ,它描述了如何防止未知插件被加载,但这不适合我的情况,因为即使是合法的插件也会是未知的。

我已成功将 .NET 4.0 安全属性应用到我的程序集,并且 MEF 正确地遵守了它们,但我不明白这如何帮助我锁定恶意代码,因为许多框架方法可能是安全威胁(例如 System.IO.File 的方法)被标记为 SecuritySafeCritical,这意味着它们可以从 SecurityTransparent 程序集访问。我在这里错过了什么吗?我可以采取一些额外的步骤来告诉 MEF 它应该为插件程序集提供互联网权限吗?

最后,我还考虑使用单独的 AppDomain 创建自己的简单沙盒插件架构,如 here 所述。 。然而,据我所知,这种技术只允许我使用后期绑定(bind)来调用不受信任的程序集中的类的静态方法。当我尝试扩展此方法来创建我的插件类之一的实例时,返回的实例无法转换为公共(public)插件接口(interface),这意味着主机应用程序无法调用它。我可以使用某种技术来跨 AppDomain 边界进行强类型代理访问吗?

对于这个问题的长度,我深表歉意;原因是展示我已经研究过的所有途径,希望有人可以建议一些新的尝试。

非常感谢您的想法,蒂姆

最佳答案

我已经接受了 Alastair Maw 的回答,因为正是他的建议和链接让我找到了一个可行的解决方案,但我在这里发布了我所做的一些细节,供其他可能试图实现类似目标的人引用。

提醒一下,我的应用程序最简单的形式包含三个程序集:

  • 将使用插件的主应用程序集
  • 定义应用程序及其插件共享的通用类型的互操作程序集
  • 示例插件程序集

下面的代码是我的真实代码的简化版本,仅显示发现和加载插件所需的内容,每个插件都有自己的AppDomain:

从主应用程序程序集开始,主程序类使用名为 PluginFinder 的实用程序类来发现指定插件文件夹中任何程序集中的合格插件类型。然后,对于每种类型,它都会创建一个 sandox AppDomain 实例(具有 Internet 区域权限),并使用它来创建发现的插件类型的实例。

创建具有有限权限的AppDomain时,可以指定一个或多个不受这些权限约束的受信任程序集。为了在此处介绍的场景中实现此目的,必须对主应用程序程序集及其依赖项(互操作程序集)进行签名。

对于每个加载的插件实例,可以通过其已知接口(interface)调用插件内的自定义方法,并且插件还可以通过其已知接口(interface)回调到主机应用程序。最后,主机应用程序卸载每个沙箱域。

class Program
{
static void Main()
{
var domains = new List<AppDomain>();
var plugins = new List<PluginBase>();
var types = PluginFinder.FindPlugins();
var host = new Host();

foreach (var type in types)
{
var domain = CreateSandboxDomain("Sandbox Domain", PluginFinder.PluginPath, SecurityZone.Internet);
plugins.Add((PluginBase)domain.CreateInstanceAndUnwrap(type.AssemblyName, type.TypeName));
domains.Add(domain);
}

foreach (var plugin in plugins)
{
plugin.Initialize(host);
plugin.SaySomething();
plugin.CallBackToHost();

// To prove that the sandbox security is working we can call a plugin method that does something
// dangerous, which throws an exception because the plugin assembly has insufficient permissions.
//plugin.DoSomethingDangerous();
}

foreach (var domain in domains)
{
AppDomain.Unload(domain);
}

Console.ReadLine();
}

/// <summary>
/// Returns a new <see cref="AppDomain"/> according to the specified criteria.
/// </summary>
/// <param name="name">The name to be assigned to the new instance.</param>
/// <param name="path">The root folder path in which assemblies will be resolved.</param>
/// <param name="zone">A <see cref="SecurityZone"/> that determines the permission set to be assigned to this instance.</param>
/// <returns></returns>
public static AppDomain CreateSandboxDomain(
string name,
string path,
SecurityZone zone)
{
var setup = new AppDomainSetup { ApplicationBase = Path.GetFullPath(path) };

var evidence = new Evidence();
evidence.AddHostEvidence(new Zone(zone));
var permissions = SecurityManager.GetStandardSandbox(evidence);

var strongName = typeof(Program).Assembly.Evidence.GetHostEvidence<StrongName>();

return AppDomain.CreateDomain(name, null, setup, permissions, strongName);
}
}

在此示例代码中,主机应用程序类非常简单,仅公开一种可由插件调用的方法。但是,此类必须从 MarshalByRefObject 派生,以便可以在应用程序域之间引用它。

/// <summary>
/// The host class that exposes functionality that plugins may call.
/// </summary>
public class Host : MarshalByRefObject, IHost
{
public void SaySomething()
{
Console.WriteLine("This is the host executing a method invoked by a plugin");
}
}

PluginFinder 类只有一个公共(public)方法,该方法返回已发现的插件类型的列表。此发现过程加载它找到的每个程序集,并使用反射来识别其合格类型。由于此过程可能会加载许多程序集(其中一些甚至不包含插件类型),因此它也在单独的应用程序域中执行,该应用程序域可能随后被卸载。请注意,由于上述原因,此类还继承了 MarshalByRefObject。由于 Type 的实例可能无法在应用程序域之间传递,因此此发现过程使用名为 TypeLocator 的自定义类型来存储每个已发现类型的字符串名称和程序集名称,这可能会导致然后安全地传回主应用程序域。

/// <summary>
/// Safely identifies assemblies within a designated plugin directory that contain qualifying plugin types.
/// </summary>
internal class PluginFinder : MarshalByRefObject
{
internal const string PluginPath = @"..\..\..\Plugins\Output";

private readonly Type _pluginBaseType;

/// <summary>
/// Initializes a new instance of the <see cref="PluginFinder"/> class.
/// </summary>
public PluginFinder()
{
// For some reason, compile-time types are not reference equal to the corresponding types referenced
// in each plugin assembly, so equality must be tested by loading types by name from the Interop assembly.
var interopAssemblyFile = Path.GetFullPath(Path.Combine(PluginPath, typeof(PluginBase).Assembly.GetName().Name) + ".dll");
var interopAssembly = Assembly.LoadFrom(interopAssemblyFile);
_pluginBaseType = interopAssembly.GetType(typeof(PluginBase).FullName);
}

/// <summary>
/// Returns the name and assembly name of qualifying plugin classes found in assemblies within the designated plugin directory.
/// </summary>
/// <returns>An <see cref="IEnumerable{TypeLocator}"/> that represents the qualifying plugin types.</returns>
public static IEnumerable<TypeLocator> FindPlugins()
{
AppDomain domain = null;

try
{
domain = AppDomain.CreateDomain("Discovery Domain");

var finder = (PluginFinder)domain.CreateInstanceAndUnwrap(typeof(PluginFinder).Assembly.FullName, typeof(PluginFinder).FullName);
return finder.Find();
}
finally
{
if (domain != null)
{
AppDomain.Unload(domain);
}
}
}

/// <summary>
/// Surveys the configured plugin path and returns the the set of types that qualify as plugin classes.
/// </summary>
/// <remarks>
/// Since this method loads assemblies, it must be called from within a dedicated application domain that is subsequently unloaded.
/// </remarks>
private IEnumerable<TypeLocator> Find()
{
var result = new List<TypeLocator>();

foreach (var file in Directory.GetFiles(Path.GetFullPath(PluginPath), "*.dll"))
{
try
{
var assembly = Assembly.LoadFrom(file);

foreach (var type in assembly.GetExportedTypes())
{
if (!type.Equals(_pluginBaseType) &&
_pluginBaseType.IsAssignableFrom(type))
{
result.Add(new TypeLocator(assembly.FullName, type.FullName));
}
}
}
catch (Exception e)
{
// Ignore DLLs that are not .NET assemblies.
}
}

return result;
}
}

/// <summary>
/// Encapsulates the assembly name and type name for a <see cref="Type"/> in a serializable format.
/// </summary>
[Serializable]
internal class TypeLocator
{
/// <summary>
/// Initializes a new instance of the <see cref="TypeLocator"/> class.
/// </summary>
/// <param name="assemblyName">The name of the assembly containing the target type.</param>
/// <param name="typeName">The name of the target type.</param>
public TypeLocator(
string assemblyName,
string typeName)
{
if (string.IsNullOrEmpty(assemblyName)) throw new ArgumentNullException("assemblyName");
if (string.IsNullOrEmpty(typeName)) throw new ArgumentNullException("typeName");

AssemblyName = assemblyName;
TypeName = typeName;
}

/// <summary>
/// Gets the name of the assembly containing the target type.
/// </summary>
public string AssemblyName { get; private set; }

/// <summary>
/// Gets the name of the target type.
/// </summary>
public string TypeName { get; private set; }
}

互操作程序集包含将实现插件功能的类的基类(请注意,它也派生自 MarshalByRefObject

此程序集还定义了 IHost 接口(interface),使插件能够回调到主机应用程序。

/// <summary>
/// Defines the interface common to all untrusted plugins.
/// </summary>
public abstract class PluginBase : MarshalByRefObject
{
public abstract void Initialize(IHost host);

public abstract void SaySomething();

public abstract void DoSomethingDangerous();

public abstract void CallBackToHost();
}

/// <summary>
/// Defines the interface through which untrusted plugins automate the host.
/// </summary>
public interface IHost
{
void SaySomething();
}

最后,每个插件都派生自互操作程序集中定义的基类并实现其抽象方法。任何插件程序集中可能有多个继承类,并且可能有多个插件程序集。

public class Plugin : PluginBase
{
private IHost _host;

public override void Initialize(
IHost host)
{
_host = host;
}

public override void SaySomething()
{
Console.WriteLine("This is a message issued by type: {0}", GetType().FullName);
}

public override void DoSomethingDangerous()
{
var x = File.ReadAllText(@"C:\Test.txt");
}

public override void CallBackToHost()
{
_host.SaySomething();
}
}

关于.net - 寻找一种实用的沙盒 .NET 插件方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4145713/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com