mcpField
R
rust-unsafe-ffi

rust-unsafe-ffi

github.com/full-stack-skills开发12111 次安装5 周前更新
代码仓库

关于 rust-unsafe-ffi

rust-unsafe-ffi 面向处理 unsafe 代码、原始内存和 C ABI 边界的 Rust 开发者,强调明确安全契约、缩小 unsafe 范围、理清所有权并验证实现。

主要功能

  1. 01论证并隔离 unsafe 操作
  2. 02记录安全契约
  3. 03谨慎处理原始内存
  4. 04明确布局、固定与 ABI
  5. 05定义狭窄的 FFI 边界
  6. 06使用 Miri 和平台测试验证

适用场景

  1. 01审查原始指针代码
  2. 02构建或审计 C ABI 绑定
  3. 03检查 FFI 边界两侧的所有权
  4. 04测试 unsafe 内存不变量

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/full-stack-skills/rust-skills --skill rust-unsafe-ffi

安装后在 Claude Code 中输入/rust-unsafe-ffi确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents/
openai.yaml
examples/
examples.md
examples/golden-unsafe/
Cargo.toml
examples/golden-unsafe/src/
lib.rs
references/
references.md

详细介绍

Rust 不安全代码与 FFI

将 unsafe 视为一项证明义务。不安全块允许执行特定操作;它不会放宽有效性、别名、初始化、生命周期、数据竞争、布局或 ABI 要求。

范围与路由

在处理原始指针、NonNull、不安全函数和 trait、联合体、MaybeUninit、ManuallyDrop、布局、固定、分配器、手动实现 Send 或 Sync、C ABI 绑定、回调、句柄和所有权转移时,使用此技能。

普通所有权设计转交给 rust-stable,并发架构转交给 rust-concurrency,UniFFI 生成的多语言绑定转交给 rust-uniffi-building,通用构建脚本转交给 rust-cargo-build,审查报告转交给 rust-code-review。

工作流

1. 说明理由并隔离不安全操作

明确指出安全 Rust 无法表达的操作。如果经审计的 crate 或标准库抽象能够保留所需行为,优先使用它。将不安全块保持精简,并启用:

#![deny(unsafe_op_in_unsafe_fn)]

unsafe fn 仍必须将每个不安全操作放在显式的 unsafe {} 块中。只有在安全封装能够在内部建立并维持所有不变式时,才应提供这种封装。

2. 先编写安全契约,再编写代码

记录:

  • 指针来源、非空性、对齐方式以及可解引用的字节范围;
  • 被指向类型的初始化和有效性要求;
  • 整个访问期间的别名和修改规则;
  • 生命周期和所有权转移,包括由谁销毁或释放值;
  • 线程安全和可重入要求;
  • ABI、布局、调用约定、整数宽度和错误约定;
  • 边界上的 panic 或外部异常行为。

对不安全的公共 API 使用 # Safety 文档,并在证明所在位置添加 // SAFETY: 注释。

3. 保守地实现原始内存操作
pub unsafe fn read_i32(ptr: *const i32) -> i32 {
    // SAFETY: The caller guarantees that ptr is aligned, initialized,
    // dereferenceable for one i32, and not concurrently mutated.
    unsafe { ptr.read() }
}
  • 仅当 ptr.add 的操作范围位于同一已分配对象内或恰好指向其后一位时,才使用它。
  • 区分对齐读取和未对齐读取;绝不为未对齐的打包字段创建引用。
  • 当值可能尚未初始化时,使用 MaybeUninit<T>,并准确跟踪在析构或调用 assume_init 前哪些元素已初始化。
  • 相比 transmute,优先使用 from_ne_bytes、带布局检查的指针转换或显式字段转换。
  • 不要用 ManuallyDrop 掩盖所有权歧义或重复析构风险。
4. 精确定义布局和固定
  • 对 C 兼容的结构体布局使用 #[repr(C)],对共享枚举使用显式 repr。
  • 只有在满足其文档规定的字段限制时,才使用 #[repr(transparent)]。
  • 将填充字节视为可能未初始化;不要通过复制结构体的原始字节来序列化它。
  • Pin<P> 仅在固定契约下保护被指向对象。它不会使普通的 Unpin 数据无法移动,也不会自动保证自引用的安全性。
  • 优先使用成熟的投影辅助工具或结构化固定模式,而不是原始自引用指针。
5. 构建精简的 FFI 边界

Edition 2024 要求在适用情况下使用不安全 extern 块和不安全属性:

use std::ffi::{c_char, CStr};

unsafe extern "C" {
    fn foreign_name() -> *const c_char;
}

pub fn name() -> Option<String> {
    // SAFETY: The foreign contract promises either null or a valid,
    // NUL-terminated string that remains alive for this call.
    let ptr = unsafe { foreign_name() };
    (!ptr.is_null()).then(|| {
        // SAFETY: Non-nullness and termination follow from the contract above.
        unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned()
    })
}
  • 使用 std::ffi 中的 C 类型或生成的绑定,不要假定 Rust 整数的宽度。
  • 在单一边界处转换可空指针、长度、所有权和错误代码。
  • 除非所选 ABI 明确允许,否则不要让展开越过 C ABI;必要时在回调中捕获 panic。
  • 明确规定回调的生命周期、线程、取消和注销行为。
  • 在同一分配器域中配对分配与释放操作。
6. 验证证明

运行适用的检查:

cargo fmt --all --check
cargo check --workspace --all-targets --all-features
cargo test --workspace --all-targets --all-features
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo miri test

还应在支持的环境中使用 Sanitizer 或 Valgrind、ABI 布局断言、C 侧集成测试、多个优化级别,以及每种受支持的操作系统和架构。Miri 不会执行任意外部代码,因此测试 Rust 侧内存契约时,应隔离或模拟外部调用。

阅读 不安全代码与 FFI 参考,了解内存类型的详细信息。阅读 执行场景,了解具有代表性的边界。

审查清单

  • unsafe 是否必要、范围最小,并且有局部依据?
  • 每项安全契约是否涵盖有效性、别名、生命周期、布局和并发?
  • 安全调用方能否违反封装层的假设?
  • 部分初始化和失败清理是否正确?
  • FFI 两侧的所有权和释放是否对称?
  • panic 或外部展开是否可能越过 ABI 边界?
  • 是否已证明手动实现的 Send 和 Sync 对每个字段和回调都安全?
  • 测试是否覆盖空指针、空值、最大值、未对齐、错误、回调和关闭路径?

完成标准

  • 记录每项调用方和实现方的安全义务。
  • 将 unsafe 块保持在最小范围,并确保安全封装不可能被安全 Rust 代码误用。
  • 验证 ABI 类型、布局、所有权、错误和展开行为。
  • 在适用情况下运行 Miri,并针对外部边界运行真实平台测试。
  • 记录任何无法在本地验证的属性。

上游来源

数据隐私

此技能不会收集、存储或传输用户数据。未经授权,不要将专有头文件、生成的绑定、崩溃转储或内存内容发送到外部服务。

评论

支持 Markdown