如果你刚入坑 Rust,看到版本号从 1.91.0 升到 1.91.1,很正常的反应可能是:

“这不就是个小补丁版吗,应该没啥好看的吧?”

但 Rust 官方博客这次发的内容很明确:1.91.1 完全不是来加新功能的,而是紧急修复 1.91.0 引入的两个回归问题,而且都和 Wasm构建目录锁(Cargo target directory locking) 这种“平时不太关心、出事就很难排查”的底层细节有关。

这篇文章带你搞清楚:

  • 1.91.1 到底修了哪两个坑?
  • 这些坑跟“安全”和“稳定性”有什么关系?
  • 你是 Rust 新手,需要在意、需要升级吗?

先看大图:1.91.1 修了哪两件事?

从官方博客 Announcing Rust 1.91.1 来看,这次只有两个核心修复:

  1. Wasm 目标下 #[link(wasm_import_module)] 的回归问题

    • 可能导致链接时报 "import module mismatch" 错误;
    • 更糟糕的是,运行时可能会“调用错函数”,出现未定义行为(Undefined Behavior),包括崩溃、静默数据损坏等。
  2. illumos 平台上 Cargo 的 target/ 目录锁失效

    • 由于标准库 File::lock 在 illumos 上一直返回 Unsupported
    • 导致 Cargo 误以为“这里不支持锁”,于是构建时完全不上锁;
    • 多个构建同时跑时,有机会互相踩产物。

听上去很底层、很偏门对吧?但这两件事背后,要么是“安全边界可能被绕过”,要么是“构建结果不再确定”,都跟“稳不稳定、安不安全”直接相关。


一、Wasm 那个坑:同名函数居然被“搞混了”

1. Wasm 是怎么认一个函数的?

在大部分常见平台(Linux / macOS / Windows)上,一个符号(函数)基本靠“名字”就能标识,比如 foobar

但在 WebAssembly(Wasm) 里,导入的函数一般有两层信息:

  • 模块名(module name):可以粗暴理解为“文件夹名”;
  • 符号名(symbol name):就像这个文件夹里的“文件名”。

同一个 world,如果分别来自模块 hello 和模块 auth,那其实是两套完全不同的逻辑。

在 Rust 里,如果你要从某个 Wasm 模块导入函数,通常像这样写:

#[link(wasm_import_module = "hello")]
extern "C" {
    pub fn world();
}

这里:

  • #[link(wasm_import_module = "hello")] 告诉编译器:
    • “这段 extern 里的函数是从 Wasm 模块 hello 导入的。”
  • world 是具体函数名。

真正标识这个函数的,是 (模块名 = “hello”, 符号名 = “world”) 这对组合

2. 1.91.0 的回归:链接报错,运行时还可能用错函数

Rust 官方的描述是:

Rust 1.91.0 在这个属性上引入了回归,当你从不同 Wasm 模块导入同名函数,而且分散在多个 crate 里时,就有可能:

  • 编译/链接时报 "import module mismatch"
  • 或者更隐蔽:编译通过,运行时却偷偷调用了错误的函数实现

举个比较“安全向”的例子:

  • 模块 auth 里有一个 check 函数,负责权限校验;
  • 模块 log 里也有一个 check,只做记录日志;
  • 不同 crate 通过 #[link(wasm_import_module = "...")] 分别从 authlog 导入 check

如果 1.91.0 这个 bug 被踩中:

  • 你以为调用的是 auth::check 来做安全校验;
  • 实际运行时却调用了 log::check,只写了条日志,什么权限也没验证。

官方博客直接提到,这种问题可能导致:

  • 未定义行为(Undefined Behavior)
  • 包括但不限于:
    • 程序崩溃;
    • 静默数据损坏(silent data corruption);
    • 逻辑上该有的安全验证被悄悄绕过。

3. 这里的“安全风险”到底在哪?

很多人一听到 Wasm,就会想到“沙箱、隔离、内存安全”这些关键词,觉得应该很安全。

从内存安全角度,Wasm 的确有天然优势;但这次问题更偏 “业务逻辑安全”

  • 如果你在 Wasm 里做的事情包括:
    • 认证 / 授权;
    • 请求过滤 / 风控;
    • 敏感操作前的预检(比如支付、修改密码);
  • 一旦“调用错函数”,结果可能是:
    • 该校验没校验;
    • 该拒绝没拒绝;
    • 或者拿错数据算了一通,结果完全不可信。

更麻烦的是:

这种 bug 在开发环境很难第一时间暴露,因为需要“多个 crate + 多个模块 + 同名导入”这套组合条件。
真正出事,往往是在比较复杂的生产环境某个部署方式刚好踩中组合。

4. 1.91.1 具体修了什么?

简单说:

  • 修复后,编译器/链接器会重新正确区分 (模块名, 符号名)
  • 不再出现“同名函数但来自不同模块”的导入被混淆的情况;
  • 也就堵上了“运行时悄悄调用错函数”的坑。

二、illumos 上 Cargo 锁失效:构建互相“踩来踩去”

第二个问题发生在 illumos 这个平台上(一个类 Solaris 的系统,虽然用户不算多,但在某些场景还是有人用)。

1. Cargo 为啥要锁 target/ 目录?

当你运行 cargo build 的时候,所有中间产物和最终二进制基本都会落在 target/ 目录里。

如果你:

  • 同时开了两个 cargo build
  • 或者 IDE 在后台默默帮你编译,前台你又手动跑了一遍;

那它们可能会对同一套文件进行读写。如果不加控制,就可能出现:

  • 一个构建刚写了一半文件,另一个构建已经来读了;
  • 多个构建互相覆盖、删写对方的产物。

为此,Cargo 会在构建时尝试对 target/ 目录做一个“锁”(lock):

  • 锁成功:表示这个构建“拿到使用权”,别的构建要么排队、要么失败;
  • 如果操作系统明确说“不支持锁”,Cargo 会选择不要锁,但尽量少踩坑。

2. 1.91.0 的改动:从自研锁改用 File::lock

在 Rust 1.91.0 之前,Cargo 用的是自己一套与系统 API 交互的锁逻辑。
到了 1.91.0,官方做了一个看起来很健康的重构:

把原来的自写锁逻辑,换成用标准库里新稳定的 File::lock 系列方法(在 1.89.0 稳定)。

这样做本身没问题,问题出在 illumos 上

  • 由于一个疏忽,File::lock 在 illumos 目标上总是返回 Unsupported
  • Cargo 收到这个返回值,就会认为“这个文件系统不支持锁”,于是:
    • 不再做任何锁操作
    • 无论实际文件系统是不是支持锁。

结果就是:

在 illumos 上,你跑多少个 cargo build 并行,Cargo 都不会帮你防止互相踩 target/

3. 这会带来什么后果?

如果你在 illumos 上:

  • 用的是共享 target/ 的构建方式;
  • 同时跑多个构建 / 测试 / CI 任务;

理论上就有可能出现:

  • 中间产物被另一份构建覆盖;
  • 某个构建读到了“半写完”的文件;
  • 最终生成的二进制处于一种“很难重现的奇怪状态”:
    • 有时运行正常,有时莫名其妙崩溃;
    • 有些 bug 在本地跑不出来,只在 CI 或生产里出现。

4. 这算安全问题吗?

这种问题更偏 “构建安全 / 供应链可靠性”,而不是那种“一看就是远程攻击向量”的漏洞。

但如果你从“可重现实验 + 确定性构建”的视角来想,它确实有隐患:

  • 构建出来的程序不再是确定的产物,带着某种“薛定谔的状态”;
  • 在一些自动化程度较高的 CI/CD 流水线里:
    • 你以为测试和上线的是同一个二进制,但实际上可能不是;
    • 某些 bug 只在生产环境被触发,很难对照源码和构建日志来定位。

1.91.1 的修复方式是:

  • 在标准库里修掉了 illumos 目标上 File::lock 总返回 Unsupported 的问题;
  • File::lock 在 illumos 上真正可用;
  • 于是 Cargo 又能在 illumos 上正常给 target/ 上锁,间接修好了回归。

三、为什么 1.91.1 这种“小版本”值得赶紧升?

先把两件事归类一下:

  • Wasm 回归

    • 影响人群:使用 #[link(wasm_import_module)] 并且跨多个 crate 导入同名函数的项目;
    • 风险类型:运行时调用错函数,导致未定义行为;
    • 可能带来:崩溃、数据损坏、业务安全逻辑被悄悄绕过。
  • illumos + Cargo 回归

    • 影响人群:在 illumos 上开发、跑 CI 或生产构建的用户;
    • 风险类型:构建目录互相踩踏,产物不再确定;
    • 可能带来:难以复现的构建 bug、线下线上不一致。

共同点是:

这俩都是 1.91.0 引入的回归(regression),不是“古早 bug 终于修了”。

换句话说:

  • 你在 1.90.x 甚至更早版本上可能没问题;
  • 反而是升到 1.91.0 后,才有机会踩到这些坑;
  • 1.91.1 的作用,更像是:
    • “把 1.91.0 本来该有的质量拉回正常水准”。

从工程实践上讲,专门修回归的小版本一般都值得尽快更新,因为:

  • 它不是在冒险加功能,而是在帮你减少奇怪问题的概率
  • 即便你现在没有踩到,也很难保证未来某个依赖升级或部署方式变更不会触发这类角落场景。

四、如果我是新手,需要做什么?

如果你目前还只是刚入门 Rust,写的都是小 demo,可能会想:

“这俩问题听着离我挺远的,我要不要管?”

我会建议你至少做三件很简单的事:

1. 看一下当前版本

rustc --version

如果看到的是 1.91.0,强烈建议你升级;
如果还在更老版本,可以考虑顺便一起拉到最新稳定版。

2. 用 rustup 升级到最新 stable

Rust 官方推荐的方式很简单:

rustup update stable

如果你之前还没装 rustup,可以到官网安装:

3. 即使现在用不到 Wasm 和 illumos,也建议跟着 stable 走

原因很现实:

  • 你将来很可能会接触 Wasm(前端集成、云函数、边缘计算等);
  • CI/CD、第三方库、各种工具的维护者,大多会以最新 stable 为参考基线
  • 提前站在一个“没有这些回归”的版本上,能省掉不少未来排查时间。

4. 如果你是维护者,有这些场景要特别留意

  • 项目里有使用 #[link(wasm_import_module)],而且有可能跨 crate 导入同名函数:
    • 升级到 1.91.1 后,重新跑一遍关键路径;
    • 尽量把“安全、校验、加密”这种逻辑的导入逻辑梳理清楚。
  • 在 illumos 上跑构建/测试/生产:
    • 升级后,留意是否有此前偶发的构建问题被“悄悄修好”;
    • 能的话,把大项目的 target/ 分开(比如按工作区或配置拆分),进一步降低互相影响的可能性。

五、用一句话总结这次更新的“安全意义”

最后帮你压缩成一段记忆点:

  • Wasm 这边:修的是“导入模块 + 符号名”这对组合被搞混导致的调用错函数问题,

    • 一旦挨揍的是安全相关逻辑,就会演变成“逻辑安全”风险。
  • illumos 这边:修的是 File::lock 在 illumos 上始终不工作,

    • 进而导致 Cargo 不再给 target/ 上锁,带来构建产物不确定的问题。

所以如果用一句比较接地气的话来说:

Rust 1.91.1 是那种“看起来只是小数点后多了个 1,其实是在修底层关键细节”的版本。
不要求你看懂所有实现,但起码别一直停在 1.91.0。


参考与延伸阅读

想继续深挖,可以从这些官方资料和 issue 开始看起: