发布 Rust 1.15.1

2017 年 2 月 9 日 · Rust 核心团队

Rust 团队很高兴地宣布 Rust 的最新版本 1.15.1。Rust 是一门专注于安全性、速度和并发性的系统编程语言。

如果您已经安装了旧版本的 Rust,获取 Rust 1.15.1 非常简单,只需运行

$ rustup update stable

如果您尚未安装 Rust,可以从我们网站的相应页面下载 Rust,并在 GitHub 上查看 1.15.1 的详细发布说明

1.15.1 stable 版本中有哪些内容?

此版本修复了两个问题:新方法 vec::IntoIter::as_mut_slice 中的一个健全性错误 (soundness bug),以及一个导致 Rust 发行版中的某些 C 组件未以 -fPIC 编译的回归问题。后者导致在某些配置(包括常见的 Linux 配置)下,可执行文件的文本段变得可写,这破坏了一项重要的攻击缓解措施,并通过增加链接器的工作量导致启动时间更长。对于主要由 Rust 组成的代码库,失去只读文本段的实际影响相对较小(因为 Rust 的类型系统是其第一道防线),但对于链接到其他代码库中的 Rust,其影响可能意外地非常大。PIC 问题是众所周知的,并非 Rust 特有,因此本文其余部分将重点关注健全性错误。

as_mut_slice 这个仅三行代码的函数中的问题在 Rust 1.15.0 发布后仅几分钟就被发现,这提醒了编写 unsafe 代码的危险性。

as_mut_sliceVec 类型的 IntoIter 迭代器上的一个方法,它提供了一个对正在迭代的缓冲区的可变视图。概念上它很简单:只需返回缓冲区的引用;实现也确实很简单,但它是 unsafe 的,因为 IntoIter 是用一个指向缓冲区的 unsafe 指针实现的

pub fn as_mut_slice(&self) -> &mut [T] {
    unsafe {
        slice::from_raw_parts_mut(self.ptr as *mut T, self.len())
    }
}

这几乎是你能想到的最简单的 unsafe 方法。你能找出错误吗?我们的审阅者没能发现!这个 API 之所以被漏掉,是因为它非常标准且代码量很小。这是一个复制粘贴错误,审阅者忽略了它。这个方法接受一个共享引用,并从中不安全地派生出一个可变引用。这完全是错误的,因为这意味着 as_mut_slice 可以用来生成指向同一个缓冲区的多个可变引用,而在 Rust 中,这是你绝对不能做的事情。

下面是一个简单的例子,展示了这个错误会让你错误地写出什么样的代码

fn main() {
    let v = vec![0];
    let v_iter = v.into_iter();
    let slice1: &mut [_] = v_iter.as_mut_slice();
    let slice2: &mut [_] = v_iter.as_mut_slice();
    slice1[0] = 1;
    slice2[0] = 2;
}

这里 slice1slice2 都引用了同一个可变切片。还要注意,创建它们的迭代器 v_iter 并未声明为可变,这很好地表明有问题。

这里的解决方案很简单,只需将 &self 改为 &mut self

pub fn as_mut_slice(&mut self) -> &mut [T] {
    unsafe {
        slice::from_raw_parts_mut(self.ptr as *mut T, self.len())
    }
}

这样,就能维护正确的唯一性不变式,一次只能创建一个可变切片,并且 v_iter 必须声明为可变才能提取出可变切片。

因此我们进行了这项更改,并发布了修复版本。在 Rust 中,我们以不破坏 API 为荣,但由于这是一个新的次要功能,并且目前的实现存在显著的健全性问题,我们决定立即发布修复,希望能赶在太多代码库使用它之前——也就是说,我们不认为这是一个需要仔细过渡的破坏性更改,而是一个必要的错误修复。有关 Rust 如何确保稳定性的更多信息,请参阅博客文章“稳定性作为交付物”、关于语言演进的RFC 1122 以及关于库演进的RFC 1105

我们仍在评估从中可以学到什么,但这很好地提醒了在编写 unsafe 代码时必须格外小心。对于如何在开发过程早期捕获这类问题,我们有一些想法,但尚未做出任何决定。

对于带来的不便,我们深表歉意。让我们继续编写代码吧。

1.15.1 的贡献者

有 2 位个人为 Rust 1.15.1 做出了贡献。感谢他们!