DotNetIsolator核心概念一文读懂:IsolatedRuntimeHost与IsolatedRuntime完全解析
【免费下载链接】DotNetIsolatorA library for running isolated .NET runtimes inside .NET项目地址: https://gitcode.com/gh_mirrors/do/DotNetIsolator
DotNetIsolator 是一个能让你的 .NET 程序在隔离环境中运行其他 .NET 代码的开源库,其核心概念就是一对黄金搭档:IsolatedRuntimeHost(宿主工厂)与IsolatedRuntime(隔离运行时实例)。本文用通俗易懂的方式,为新手完整解析这两个核心类的职责、用法与最佳实践,帮你快速搞懂如何在自己的项目中搭建 .NET 沙箱环境。
🧩 一句话理解:IsolatedRuntimeHost 负责"造沙箱",IsolatedRuntime 负责"在沙箱里跑代码"。想在自己的项目里安全地执行不可信 .NET 代码,掌握这两者是第一步。
DotNetIsolator 是什么?隔离 .NET 运行时的工作原理
DotNetIsolator 的底层方案并不神秘:每个隔离运行时实际上是一个WebAssembly 沙箱,由 dotnet-wasi-sdk 构建,运行在 Wasmtime 之上。
这意味着每个IsolatedRuntime都拥有:
| 特性 | 说明 |
|---|---|
| 🧠 独立内存空间 | 与宿主进程完全隔离,无法直接读写宿主内存 |
| 🚫 无系统权限 | 无法直接访问宿主机器的磁盘、网络和操作系统 API |
| 📦 独立程序集 | 通过宿主"喂"给它程序集,才具备运行能力 |
| 🔁 双向调用 | 宿主可以调隔离环境,隔离环境也能回调宿主 |
举个最直观的例子,用Invoke在隔离环境里运行一段 lambda,你会看到宿主机与沙箱内的架构信息完全不同:
using var host = new IsolatedRuntimeHost().WithBinDirectoryAssemblyLoader(); using var runtime = new IsolatedRuntime(host); Console.WriteLine($"I'm running on {RuntimeInformation.OSArchitecture}"); runtime.Invoke(() => { Console.WriteLine($"I'm running on {RuntimeInformation.OSArchitecture}"); });输出结果大致为:
I'm running on X64 I'm running on Wasm宿主是 X64,沙箱里却是 Wasm——这就是"隔离 .NET 运行时"的直观体现。
IsolatedRuntimeHost 核心解析:为何它是"一次性"的宿主工厂
IsolatedRuntimeHost是整个体系的入口,它负责启动 Wasmtime 引擎、加载 WASI 模块、配置程序集加载器。它的定义位于 IsolatedRuntimeHost.cs。
IsolatedRuntimeHost 的三大职责
- 启动 Wasmtime 引擎:这一步大约耗时400ms,属于"重资产",所以应该全局只创建一次,然后反复复用(源码见 IsolatedRuntimeHost.cs)。
- 配置程序集加载:隔离环境里没有现成的代码,需要由宿主"投喂"程序集字节。
- 不保存运行时状态:Host 本身不持有任何与具体沙箱相关的状态,因此可以安全地跨线程共享。
如果你在使用依赖注入(DI),完全可以把IsolatedRuntimeHost注册为单例(Singleton)。
最快配置方法:WithBinDirectoryAssemblyLoader
最常用的配置方式就是"从宿主程序的 bin 目录加载程序集":
using var host = new IsolatedRuntimeHost() .WithBinDirectoryAssemblyLoader();它的实现逻辑很简单:根据程序集名去 bin 目录找对应的.dll文件并读取字节(见 IsolatedRuntimeHost.cs)。
使用这一配置后,你就可以:
- 在隔离环境里调用 lambda 方法(方法代码就在 bin 目录的 DLL 中);
- 在隔离环境里实例化任意类型的对象;
- 在宿主与沙箱之间传递、返回自定义类型(自动通过 MessagePack 序列化)。
⚠️ 注意:WithBinDirectoryAssemblyLoader并不会让沙箱代码逃逸出隔离边界。即使它能加载宿主程序集,也只能在沙箱内使用,无法直接访问宿主资源。
更严格的程序集加载控制:WithAssemblyLoader
如果你希望精细控制"哪些程序集可以被加载",可以用WithAssemblyLoader自定义加载回调:
using var host = new IsolatedRuntimeHost() .WithAssemblyLoader(assemblyName => { if (assemblyName == "MyAssembly") { return File.ReadAllBytes("some/path/to/MyAssembly.dll"); } return null; // 未知程序集,交给其他 loader });你还可以注册多个加载器,按注册顺序依次尝试,直到某个加载器返回非空字节为止(见 IsolatedRuntimeHost.cs)。此外还有WithDirectoryAssemblyLoader可指定任意目录。
IsolatedRuntimeHost 的隐藏能力:注册 WASI 导入与回调
在构造函数里,Host 会通过Linker.DefineWasi()注册 WASI 导入,并定义两个名为dotnetisolator模块的宿主函数:request_assembly(处理程序集请求)与call_host(处理来自沙箱的宿主调用),见 IsolatedRuntimeHost.cs。
call_host的实现会把调用转发给当前Store关联的IsolatedRuntime,再交给AcceptCallFromGuest处理(见 IsolatedRuntimeHost.cs)——这正是"沙箱反向调用宿主"的关键通道。
IsolatedRuntime 核心解析:轻量级沙箱实例
IsolatedRuntime是真正"跑代码"的地方,定义位于 IsolatedRuntime.cs。它持有Store、Instance、内存引用以及一系列 WASM 导出函数,每个实例都是完全独立的沙箱。
创建 IsolatedRuntime 只需两步
using var host = new IsolatedRuntimeHost().WithBinDirectoryAssemblyLoader(); using var runtime = new IsolatedRuntime(host);与 Host 的 400ms 启动成本不同,每个 runtime 实例化大约只需 8ms,所以你可以非常轻量地按需创建:
using var runtime1 = new IsolatedRuntime(host); using var runtime2 = new IsolatedRuntime(host);想开多少个沙箱就开多少个,它们之间互不干扰。这也是 DotNetIsolator 最吸引人的设计之一:一个昂贵的引擎 + N 个廉价的沙箱。
在隔离环境里调用 lambda:最直观的入口
IsolatedRuntime.Invoke是最常用的调用方式,它支持捕获闭包变量,并自动用 MessagePack 序列化跨边界传递:
var person1 = new Person(3); var person2 = new Person(9); var sumOfAges = runtime.Invoke(() => { // 这段代码运行在隔离环境里 return person1.Age + person2.Age; }); Console.WriteLine($"The isolated runtime calculated the result: {sumOfAges}"); record Person(int Age);注意:lambda 对捕获对象或静态字段的修改只会在沙箱内生效,不会影响宿主中的对象,因为两边没有共享内存。实现细节可参考 IsolatedRuntime.cs。
在隔离环境里创建对象:IsolatedObject
如果不想依赖 bin 目录加载器,也可以手动在沙箱里创建对象再调用其方法:
// 泛型 API IsolatedObject obj1 = runtime.CreateObject<Person>(); // 字符串 API(宿主项目未引用该程序集时更适用) IsolatedObject obj2 = runtime.CreateObject("MyAssembly", "MyNamespace", "Person");创建后即可调用方法:
isolatedObject.InvokeVoid("DoSomething", 123); // 无返回值 TimeSpan result = isolatedObject.Invoke<bool, TimeSpan>("GetAge", true); // 有返回值也可以先用FindMethod拿到方法引用(类似MethodInfo),稍后再调用(见 IsolatedObject.cs)。IsolatedMethod的完整调用重载见 IsolatedMethod.cs。
⚠️ 当前限制:
CreateObject要求类型有无参构造函数,带参构造的支持尚未实现(见 NOTES.md 中的 TODO 列表)。
隔离环境反向调用宿主:RegisterCallback 双向通道
沙箱代码也能回调宿主!宿主先注册一个命名回调:
using var runtime = new IsolatedRuntime(host); runtime.RegisterCallback("addTwoNumbers", (int a, int b) => a + b); runtime.RegisterCallback("getHostTime", () => DateTime.Now);然后在沙箱代码里通过DotNetIsolatorHost.Invoke调用:
var sum = DotNetIsolatorHost.Invoke<int>("addTwoNumbers", 123, 456); var hostTime = DotNetIsolatorHost.Invoke<DateTime>("getHostTime");宿主侧的接收逻辑在 IsolatedRuntime.cs 的AcceptCallFromGuest中实现,它会根据回调签名反序列化参数并动态调用。测试用例可以参考 HostCallbackTest.cs。
核心概念对比表:Host vs Runtime 一张表看懂
| 对比维度 | IsolatedRuntimeHost | IsolatedRuntime |
|---|---|---|
| 🎯 定位 | 宿主工厂(重资产) | 沙箱实例(轻资产) |
| 💰 初始化成本 | 约 400ms,建议只建一次 | 约 8ms,可随意创建 |
| 🧬 状态 | 无运行时状态,可共享/单例 | 每个实例完全独立 |
| 🔧 主要职责 | 引擎、链接器、程序集加载配置 | 内存、方法调用、对象管理 |
| 🔁 对应源码 | IsolatedRuntimeHost.cs | IsolatedRuntime.cs |
记住这个黄金法则:Host 造一次、反复用;Runtime 按需建、随意开。
快速上手指南:从零搭建你的第一个 .NET 沙箱
想立刻体验?按照以下 4 步即可:
第 1 步:安装 NuGet 包
dotnet add package DotNetIsolator --prerelease第 2 步:创建 Host 并配置程序集加载
using var host = new IsolatedRuntimeHost() .WithBinDirectoryAssemblyLoader();第 3 步:创建 Runtime 并在沙箱内执行代码
using var runtime = new IsolatedRuntime(host); var isolatedEnvInfo = runtime.Invoke(GetEnvironmentInfo);第 4 步:对比宿主与沙箱的环境差异
static EnvInfo GetEnvironmentInfo() { var sysRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? "(Not set)"; return new EnvInfo( Environment.GetEnvironmentVariables().Count, $"SystemRoot={sysRoot}"); } record EnvInfo(int NumEnvVars, string ExampleEnvVar);输出类似:
Real env: 64 entries, including SystemRoot=C:\WINDOWS Isolated env: 0 entries, including SystemRoot=(Not set)宿主能看到的 64 个环境变量,沙箱里一个都没有——隔离效果立竿见影。
安全注意事项:把隔离当安全边界前必读
如果你打算把 DotNetIsolator 的隔离作为关键安全边界,请务必了解:
- 🧪 这是实验性预发布包,尚未经过安全审查,可能存在让沙箱代码影响宿主的缺陷;
- 🛡️ WebAssembly 本身是久经考验的沙箱技术(浏览器多年运行不可信 WASM 模块),但 Wasmtime 与浏览器实现不同;
- 🔍 WebAssembly 安全模型并未直接解决 Spectre 这类侧信道攻击问题。
结论:作为多层安全体系中的其中一层,它表现不错;但不要指望它单枪匹马抵御恶意代码。如果你的场景只是"隔离管理自己的代码"而非"运行不可信代码",大部分安全顾虑其实不适用。
总结:掌握这两个核心类,你就掌握了 DotNetIsolator
- IsolatedRuntimeHost:一次创建、全局复用,负责引擎与程序集加载配置,是沙箱的"总装车间";
- IsolatedRuntime:按需创建、互不干扰,负责实际执行与双向调用,是沙箱的"生产车间";
- 两者配合,即可在 .NET 中轻松实现隔离的 .NET 运行时,隔离磁盘、网络、操作系统等宿主资源。
想深入了解源码细节?推荐阅读 IsolatedRuntimeHost.cs、IsolatedRuntime.cs,以及测试用例 StartupTest.cs、MethodInvocationTest.cs 和 ObjectCreationTest.cs,对照示例代码学习效果最佳。动手试试吧!🚀
【免费下载链接】DotNetIsolatorA library for running isolated .NET runtimes inside .NET项目地址: https://gitcode.com/gh_mirrors/do/DotNetIsolator
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考