资讯动态

Astrid Host ABI 系统调用面(The Syscall Surface):WIT 契约、零 WASI 导入与能力门控全解析

发布时间:2026/9/28 2:31:01 来源:尧图企业网站定制
文档教程【免费下载链接】bookThe canonical reference for Astrid: kernel, capsules, host ABI, IPC, and the security model.项目地址https://gitcode.com/gh_mirrors/book269/book点击查看免费下载导读本文深入解析 Unicity Astrid OSAstrid内核中胶囊capsule访问宿主操作系统的唯一合法通道——Host ABI 系统调用面。系统调用面整体以 WIT 类型化定义、按版本冻结、逐调用做能力门控capability-gated并按主体principal隔离作用域全工具链中不存在任何wasi:*导入。读完本文你将掌握13 个astrid:*WIT 包的领域划分与冻结规则、WASM 字节边界的资源句柄机制、wasm32-unknown-unknown目标与自定义 getrandom 后端的落地方式、按导出拆分的 guest worlds 设计以及错误类型与多版本共存的 ABI 演进纪律。系统调用面胶囊与宿主 OS 的唯一通道在 Astrid 的安全模型里系统调用并不是传统意义上的内核 trap 指令而是胶囊代码与宿主 OS 之间每一次合法交互的必经之口。每一个文件读取、网络连接、日志行、随机字节都必须流过这条被 WIT 类型化、被版本冻结、被能力门控、按主体隔离的调用面。这一设计的前提是 内核本身是哑的内核不做策略决策只执行由 胶囊清单声明的能力白名单 和主体配额所约束的调用。系统调用面由此成为可审计、可策略化的单点——审计日志无需对 WASI 等低级 I/O做豁免因为根本没有这类旁路。一个关键事实支撑了整条审计链的完备性WIT 导入列表就是胶囊能做的一切宿主调用集合。astrid.audit.*因此可以无缺口地记录每一次内核交互不存在任何绕过策略的 WASI 例外路径。WIT 即契约语言13 个按领域拆分的包内核暴露给胶囊的每一个接口都在sdk-rust/contracts/host/即unicity-astrid/wit子模块下的 WIT 文件中声明组织方式是每个领域一个包WIT 包领域astrid:io1.0.0基础 I/Oerror、poll、streamsastrid:fs1.0.0文件系统VFS scheme 路径astrid:ipc1.0.0事件总线 pub/subastrid:kv1.0.0持久化键值存储astrid:net1.0.0Unix 套接字、TCP、UDP、DNSastrid:http1.0.0带 SSRF 防护的出站 HTTPastrid:sys1.0.0日志、配置、时钟、熵、能力astrid:process1.0.0宿主进程派生仅桌面内核astrid:uplink1.0.0平台桥接注册astrid:elicit1.0.0交互式安装/升级提示astrid:approval1.0.0人在回路审批astrid:identity1.0.0外部平台身份解析astrid:guest1.0.0Guest 导出 worlds内核调进胶囊每个 WIT 文件都携带固定注释Frozen per the ABI evolution discipline (RFC: host_abi). Shape changes ship as a new file at a new version path; never edit this file.按照 ABI 演进纪律冻结形状变更以新文件、新版本路径发布绝不编辑本文件。这是冻结核心规则已发布的接口不可变新行为意味着新版本的新文件。wasmtime linker 在加载时强制精确的(package, version)结构类型匹配因此新旧胶囊可以无 flag day 地共存。关于这条纪律的完整背景、双 linker 注册与 CI 校验现状参见 ABI 演进。字节边界Component Model 二进制编码与资源句柄在 WIT 类型之下是 WebAssembly Component Model 的二进制编码。内核侧用 wasmtime 的bindgen!宏、Guest 侧用wit_bindgen::generate!各自从同一份 WIT 文件生成 Rust 模块。链接时 Component Model linker 将 Guest 声明的每个import与已注册的宿主实现匹配——类型系统强制执行匹配参数写错是编译期错误而非运行期 panic。资源类型以整数句柄跨越边界。宿主管维护每个 store 一张资源表Guest 持有类型化的ResourceT包装。当 Guest 丢弃某个资源句柄时component-model 运行时会调用宿主的析构函数释放底层的 OS 对象文件描述符、TCP 连接、进程句柄。胶囊代码从不显式调用close或unsubscribe。with:块把四个基础资源类型映射到 wasmtime-wasi 的存储类型// core/crates/astrid-capsule/src/engine/wasm/bindings.rs with: { astrid:io/poll1.0.0.pollable: wasmtime_wasi::p2::DynPollable, astrid:io/error1.0.0.error: wasmtime_wasi::p2::IoError, astrid:io/streams1.0.0.input-stream: wasmtime_wasi::p2::DynInputStream, astrid:io/streams1.0.0.output-stream: wasmtime_wasi::p2::DynOutputStream, },这一设计复用了 wasmtime-wasi 现成的存储类型基于Future的包装却没有导入任何 wasiHosttrait 实现。每一个poll、block、read、write、splice都由 Astrid 自己的代码在engine/wasm/host/io.rs中实现并接线审计记录、主体作用域和取消令牌。借用的是存储类型行为完全属于 Astrid。astrid:io1.0.0并非wasi:io的再导出而是形状相同、契约不同的 Astrid 自有重实现pollable.block()与poll.poll(...)与调用胶囊的取消令牌竞争。胶囊卸载时阻塞调用立即返回cancelled而不是让宿主任务悬空。流上的每次读、写、skip、splice 都按主体审计记录传输字节数与耗时。Pollable 与流句柄受按主体配额限制。超限时分配句柄的宿主函数返回类型化错误quota而非运行期 trap。一个胶囊 store 中创建的 pollable 无法传给另一个胶囊——wasmtime 资源表边界强制隔离。poll的每次调用上限是256 个 pollables。这个数字经过精确设计一个胶囊占满 IPC 订阅配额128 个订阅时加上其全部 TCP、UDP、HTTP、进程流 pollables仍可在单次调用中全部等待。详细的 poll 组合与subscribe-readiness用法参见 Host 包IPC、Net、HTTP、Sys、Process。streams接口的splice是代理/转发类胶囊的首要吞吐原语它在宿主侧把字节从input-stream搬到output-stream不必逐字节穿越 WASM 边界。例如把 HTTP 响应体转进 TCP 连接胶囊只需调用output_stream.splice(http_stream.body_stream(), len)内核负责读-写循环。零 WASI 导入唯一的构建目标Astrid 胶囊以wasm32-unknown-unknown为目标。该目标没有 WASI 运行时也没有任何 WASI 专属导入。规范的胶囊构建配置在.cargo/config.toml里只有几行# capsules/astrid-capsule-cli/.cargo/config.toml [build] target wasm32-unknown-unknown [target.wasm32-unknown-unknown] rustflags [--cfggetrandom_backend\custom\]内核 linker 只注册astrid:*接口。如果某个胶囊意外携带wasi:*导入会在加载实例化阶段直接失败报错 interface not found——这是刻意为之的姿态。configure_kernel_linker中的注释说得很直白// core/crates/astrid-capsule/src/engine/wasm/mod.rs /// Zero wasi:* registration. The Astrid-canonical guest target is /// wasm32-unknown-unknown, capsules produce wasm with zero wasi:* /// imports, every host call going through audited astrid:* interfaces. /// A capsule that somehow ships with a wasi:* import ... fails to /// instantiate at load time with a clear interface not found error, /// that is the intended posture, not a bug to paper over. pub fn configure_kernel_linker( linker: mut wasmtime::component::LinkerHostState, ) - wasmtime::Result() { bindings::Kernel::add_to_linker::HostState, wasmtime::component::HasSelfHostState( linker, |state| state, ) }对审计链而言后果是直接的WIT 导入列表就是胶囊能做宿主调用的全集astrid.audit.*记录每次内核交互而没有审计缺口不存在可被豁免于策略的低级 I/O WASI 后门。wasm32-unknown-unknown是唯一合法的胶囊构建目标。按导出拆分的 Guest Worldsastrid:guest1.0.0包定义了四个 world一个生命周期导出对应一个// core/crates/astrid-capsule/wit-staging/deps/astrid-guest/guest1.0.0.wit world interceptor { use lifecycle.{capsule-result}; export astrid-hook-trigger: func(action: string, payload: listu8) - capsule-result; } world background { export run: func(); } world installable { export astrid-install: func(); } world upgradable { export astrid-upgrade: func(); }这个拆分是深思熟虑的。在 Component Model 中world 声明的每个导出都必须出现在编译产物里。如果把四个合并进一个 world每个胶囊就都得为未实现的导出提供 stub内核还得解析 wasm 二进制来区分真实实现与工具链 stub。按导出拆分 world把声明放在实现所在处只处理拦截器流量的胶囊只 includeastrid:guest/interceptor1.0.0二进制干干净净。内核在加载时、实例化之前通过扫描 wasm 二进制检测run导出是否存在wasm_exports_contain_run位于engine/wasm/mod.rs。run-loop 胶囊获得一个专用Store纯拦截器胶囊获得一个用于并发调用的 store 池。一个典型的拦截器胶囊 worldworld my-capsule { include astrid:guest/interceptor1.0.0; import astrid:ipc/host1.0.0; import astrid:sys/host1.0.0; }同时跑后台循环并接受安装的胶囊加上其余 includeworld my-capsule { include astrid:guest/interceptor1.0.0; include astrid:guest/background1.0.0; include astrid:guest/installable1.0.0; import astrid:ipc/host1.0.0; import astrid:uplink/host1.0.0; }合成 SDK 胶囊 Worldastrid-sysastrid-sys是低层 Guest 绑定 crate。它在单个合成 world名为capsule中导入全部宿主包// sdk-rust/astrid-sys/src/lib.rs wit_bindgen::generate!({ inline: package astrid-sdk:capsule; world capsule { import astrid:io/error1.0.0; import astrid:io/poll1.0.0; import astrid:io/streams1.0.0; import astrid:fs/host1.0.0; import astrid:ipc/host1.0.0; import astrid:kv/host1.0.0; import astrid:net/host1.0.0; import astrid:http/host1.0.0; import astrid:sys/host1.0.0; import astrid:process/host1.0.0; import astrid:uplink/host1.0.0; import astrid:elicit/host1.0.0; import astrid:approval/host1.0.0; import astrid:identity/host1.0.0; include astrid:guest/interceptor1.0.0; include astrid:guest/background1.0.0; include astrid:guest/installable1.0.0; include astrid:guest/upgradable1.0.0; } , path: wit-staging, pub_export_macro: true, generate_unused_types: true, generate_all, });注意这不是最终胶囊面向的 world而是生成期的并集——一次生成所有可能的宿主调用与 Guest 导出的类型化 Rust 绑定通过一条pub use generated::*;暴露。胶囊作者通常使用基于astrid-sys之上的人性化包装astrid-sdkastrid-sdk-macros的#[capsule]过程宏会自动生成impl Guest与export!()调用。astrid-sys的build.rs负责 WIT 暂存staging处理两种路径unicity-astrid/wit子模块存在时清理并从contracts/host/重新暂存wit-staging/deps/astrid-pkg/子模块缺失时发布后的 crate、尚未git submodule update --init的全新克隆跳过暂存使用随 crate 附带的已提交wit-staging/。两条路径产出完全相同的布局供wit_bindgen::generate!读取。还有一个值得注意的细节generate!调用中没有additional_derives。源码注释记录了原因——v1 之前该 crate 曾对所有生成类型 blanket 派生serde::Serialize / Deserialize但资源类型下这是不健全的资源句柄通过Drop拥有内核侧状态无法被序列化。astrid-sdk的包装器在边界处把 record 转成 serde 友好的形状原始 WIT 类型保持不可序列化。自定义 getrandom 后端wasm32-unknown-unknown没有平台 RNG。getrandomcrate经uuid与astrid-types传递引入在该目标默认没有后端。没有 shim 的话HashMap构造会在首次请求哈希种子时直接 panic。astrid-sys以单个#[unsafe(no_mangle)]函数提供 getrandom 0.4 custom-backend 协议所需的 shim// sdk-rust/astrid-sys/src/lib.rs #[cfg(all(target_arch wasm32, getrandom_backend custom))] #[unsafe(no_mangle)] unsafe extern Rust fn __getrandom_v03_custom( dest: *mut u8, len: usize, ) - Result(), getrandom::Error { const CHUNK: usize 4096; let mut written 0usize; while written len { let want core::cmp::min(CHUNK, len - written); let chunk generated::astrid::sys::host::random_bytes(want as u64) .map_err(|_| getrandom::Error::new_custom(1))?; if chunk.is_empty() { return Err(getrandom::Error::new_custom(2)); } let take core::cmp::min(chunk.len(), want); unsafe { core::ptr::copy_nonoverlapping(chunk.as_ptr(), dest.add(written), take); } written take; } Ok(()) }后端由每个胶囊.cargo/config.toml中针对wasm32-unknown-unknown目标设置的--cfggetrandom_backendcustom激活。在非 wasm32 构建宿主工具、过程宏、开发者机器上的测试中#[cfg]守卫完全省略该符号使用平台的默认 RNG。底层的宿主函数是astrid:sys/host.random-bytes。WIT 契约把每次调用封顶在 4096 字节shim 以CHUNK为步长循环来满足任意大小的请求。random-bytes是少数有意不做审计的宿主函数之一它只读、无副作用、被标准库代码高频调用逐条记录只会产生噪音而非有效信号// core/crates/astrid-capsule/wit-staging/deps/astrid-sys/sys1.0.0.wit /// Fill the callers requested length with cryptographically secure /// random bytes from the hosts OS-level CSPRNG. /// /// length is capped at 4096 bytes per call. Larger requests return /// too-large. /// Audit: not recorded (read-only, no side effects). random-bytes: func(length: u64) - resultlistu8, error-code;把 shim 定义在astrid-sys而非每个胶囊里使路由集中化——任何依赖astrid-sdk的胶囊零配置获得可用 RNG。异步宿主函数不钉死 tokio worker大多数宿主函数在 WIT 层是同步的宿主侧快速执行。两个领域例外astrid:ipc/host的subscription.recv会阻塞直到消息到达astrid:http/host的三个函数http-request、http-stream-start、[method]http-stream.read-chunk等待网络 I/O。内核的bindings.rs恰好把这些函数标记为async// core/crates/astrid-capsule/src/engine/wasm/bindings.rs imports: { astrid:io/streams: trappable, astrid:ipc/host.[method]subscription.recv: async, astrid:http/host.http-request: async, astrid:http/host.http-stream-start: async, astrid:http/host.[method]http-stream.read-chunk: async, },启用 Component Model 的异步支持后Guest 调用这些函数时宿主侧.await结果而不是用block_in_place在整个调用期间钉死一个 tokio worker。其余宿主函数publish、subscribe、kv、sys不在编排热路径上保持同步。astrid:sys接口运行期工具域astrid:sys1.0.0覆盖不属于特定 I/O 领域的运行期工具get-config读取胶囊[config]清单段的值。secret 类型键走 SecretStore 而非清单。get-caller返回当前调用的执行主体、源胶囊 UUID 与消息时间戳。log发出归属于调用胶囊的结构化日志写入主体的按日轮转日志目录。signal-ready通知内核 run-loop 胶囊已建好订阅、可以接收消息。clock-ms与clock-monotonic-ns返回墙钟与单调时间。sleep-ns阻塞至多 60 秒/次若睡眠期间胶囊卸载则返回cancelled。random-bytes从宿主 CSPRNG 填充缓冲区见上节。check-capsule-capability查询能力注册表中某胶囊 UUID 的具名能力。故障关闭未知 UUID 返回allowed: false注册表本身不可查时返回类型化错误registry-unavailable。sys的error-code变体值得仔细读variant error-code { capability-denied, config-key-reserved, too-large, registry-unavailable, // fail-closed sentinel cancelled, unknown(string), }registry-unavailable是独立变体而非并入unknown——因为下游调用者必须能区分该能力不存在与注册表不可达。一个基于其他胶囊能力做条件行为的胶囊必须把registry-unavailable视为拒绝而非批准。逐调用的能力门控每一个触碰胶囊自身内存之外资源的宿主函数都会对照Capsule.toml [capabilities]中清单声明的能力做门控。capability-denied错误变体存在于每个领域的error-code类型上。内核每次调用都检查能力而非加载时检查一次——因此会话中途被撤销的能力下一次调用立即生效。能力门控的完整请求路径从[capabilities]声明经ManifestSecurityGate到宿主函数检查参见 能力门控其中包含net、fs_read/fs_write、host_process、net_bind、net_connect、identity、allow_prompt_injection等字段的逐一语义、路径 schemecwd://、home://、*的解析规则、路径穿越拒绝逻辑与审计日志 target。IPC 接口区分publish主体从调用上下文标注为verified与publish-as主体由 uplink 主张、标注为claimed。接收消息的胶囊在敏感操作上必须检查principal-attribution变体——claimed主体是 uplink 断言的、内核未验证的// astrid:ipc1.0.0 variant principal-attribution { verified(string), // kernel-checked, safe for capability decisions claimed(string), // uplink-asserted, treat as caller input system, }该区分出现在收到的每个ipc-message信封上不是会话级属性subscription.recv返回的多消息批次可能同时含verified与claimed消息必须逐条独立检查。错误类型设计结构化而非字符串每个领域定义自己的类型化error-code变体。共同模式capability-denied永远是独立分支绝不落入unknown(string)字符串。捕获错误的胶囊无需字符串匹配即可对其分支。unknown(string)携带 WIT 契约未预见的宿主细节。其内容明确是尽力而为、不属契约。astrid:fs的错误字符串从不包含宿主真实路径、IP 地址、UUID 或能力名WIT 注释有载。资源特定错误如boundary-escapefs、airlock-rejectednet/http、cas-mismatchkv是一等分支调用者无需解析即可处理。astrid:io/streams的错误类型与 WASI 形状完全一致便于 SDK 作者统一推理variant stream-error { last-operation-failed(error), // downcastable to domain-specific code closed, }last-operation-failed中的error资源可通过领域特定函数向下转型。例如以流错误形式暴露的 TCP 读取失败可向下转型为astrid:net/host.error-code以区分connection-reset与timeout。各包完整错误变体与宿主实现位置参见 Host 包文件系统、IO 与存储 和 Host 包审批、身份、Uplink。ABI 稳定性与多版本共存每个 WIT 文件钉在1.0.0。新版本发布时是一个新路径上的新文件。两个版本在内核 linker 中独立注册// from bindings.rs comment // When a new frozen version ships (e.g. host/ipc1.1.0.wit), add it // here as an additional import AND register a second add_to_linker // call, the wasmtime Component Model linker enforces exact // (package, version) matches, so multiple versions must be registered // explicitly to allow old and new capsules to coexist.Guest 侧同理astrid-sys/src/lib.rs在 inline world 中把新版本加为额外import。钉在旧版本的胶囊按其旧接口原样解析基于新版本构建的胶囊解析新接口。新版本接口发布时没有胶囊需要重建。这意味着胶囊编译产物里的版本号就是它构建时所针对的 ABI而不是当前内核偏好运行的版本。内核必须注册它想支持的每个版本导入内核未注册版本的胶囊会以清晰的 linker 错误加载失败。多版本注册的完整模式双bindgen! 双add_to_linker、WIT 仓库布局与build.rs暂存逻辑、CI 校验状态参见 ABI 演进。实践要点小结胶囊作者面向wasm32-unknown-unknown构建.cargo/config.toml设置getrandom_backendcustomrustflag用astrid-sdk而非裸astrid-sys编写按实际实现的生命周期导出 include 对应 guest world在Capsule.toml里显式声明[capabilities]、[publish]、[subscribe]因为空列表就是拒绝一切。安全审查能力检查发生在每次调用而非加载时claimed主体不可用于授权决策registry-unavailable视为拒绝流经splice与poll的 I/O 全量审计唯random-bytes只读、高频例外。ABI 演进绝不编辑已冻结的 WIT 文件新形状 新文件 新版本路径 内核侧额外注册 astrid-sys额外 import曾发布的任何版本都须显式注册才能让新旧胶囊共存。延伸阅读能力门控从[capabilities]到ManifestSecurityGate的完整请求路径ABI 演进冻结文件规则、双 linker 注册与 CI 校验The Capsule Manifest and EnginesCapsule.toml全量 schema 与三引擎组合Host 包文件系统、IO 与存储astrid:fs、astrid:kv、astrid:io的 WIT 契约与实现状态Host 包IPC、Net、HTTP、Sys、Process五个领域包的 SDK 用法与配额Host 包审批、身份、Uplink人在回路与平台桥接包赞分享文档教程【免费下载链接】bookThe canonical reference for Astrid: kernel, capsules, host ABI, IPC, and the security model.项目地址https://gitcode.com/gh_mirrors/book269/book点击查看免费下载相关推荐从零到出图10.5GHz 相控阵雷达 AERIS-10 完整实践从零到出图10.5GHz 相控阵雷达 AERIS 10 完整实践 AERIS 10 是一个围绕 10.5GHz 的开源相控阵雷达项目硬件文档、Verilog文档教程IronClaw Reborn WASM 运行时契约解析从 WIT ABI 到沙箱安全边界IronClaw Reborn WASM 运行时契约解析从 WIT ABI 到沙箱安全边界 IronClaw 的 Reborn 二进制路径通过一套统一的组件模人工智能AI 应用交互助手AI AgentIronClaw Reborn Host Runtime 契约能力义务、可见能力面与受控 HTTP 出口的源码级解析IronClaw Reborn Host Runtime 契约能力义务、可见能力面与受控 HTTP 出口的源码级解析 导读 ironclaw_host_run人工智能AI 应用交互助手AI Agent上一篇SWE-agent项目中使用Azure OpenAI API密钥的配置指南下一篇RuView ADR-153用纯 Rust 构建 IEEE 802.11bf-2025 前向兼容 WLAN 感知协议层创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑