LWN:用 Rust 在内核跟踪数据可信度!

关注了就能看到更多这么棒的文章哦~

Tracking trust with Rust in the kernel

By Daroc Alden
September 3, 2025
Gemini flash translation
https://lwn.net/Articles/1034603/

Linux 内核必须处理许多不可信的数据源:比如用户空间、网络连接和可移动存储。即使其中一个发送了混乱(或恶意)的数据,内核也必须保持安全。Benno Lossin 一直致力于为内核 Rust 代码开发一个应用程序接口(API),旨在减少基于用户空间数据做出错误决策的可能性。这项工作现已进入其第四个修订版本,Lossin 已要求内核开发者对其进行试验,以发现仍然存在的问题,这使得现在是审视这个提议 API 的好时机。

这种核心方法,与 Rust 中的许多其他事物一样,都围绕着类型系统(type system)展开。Lossin 的补丁集引入了一个新类型 `Untrusted`,它将数据标记为源自不可信来源,因此需要特别谨慎。Rust 禁止尝试访问被 `Untrusted` 包装的值。这个类型是一种“透明”结构,这意味着它在内存中的布局与它所包装的类型完全相同。例如,`Untrusted<u8>` 就是一个字节。因此,该类型没有运行时开销,所以它可以在类型系统中用作一种标记,用于表示未经验证就来自用户空间的数据。这使得它不可能被意外地传递给期望普通内核数据的函数。

Lossin 补丁集的大部分内容是关于 `Untrusted` 的文档,以及一些用于操作不可信值的实用函数。对于不可信值的常见数据结构,特别是切片(slices)(长度在运行时确定的数组)和向量(vectors)(堆上可增长的数组),也有专门的支持。当处理一个应该用不可信数据填充的现有缓冲区(buffer)时,文档建议这样编写接口:

pubfnread_from_userspace(buf: &mut[Untrusted<u8>])

该函数接受一个不可信字节切片的可变引用(mutable reference),并会用用户空间数据填充它。这些数据随后可以直接复制回用户空间,而无需解包(unwrap)。然而,如果尝试在内核中实际使用不可信对象的值,将导致编译器错误。Lossin 建议采用这种形式的 API,因为将 `&mut Untrusted<[u8]>`(一个对不可信字节切片的可变引用)转换为 `&mut [Untrusted<u8>]`(一个对单独不可信字节切片的可变引用)可以自动完成——由于 `Untrusted` 的 `DerefMut` 实现,编译器会在需要时插入转换——但反向转换则需要显式的函数调用。如果 Rust 开发者养成以这种方式编写涉及 `Untrusted` 的 API 的习惯,那么使用起来的麻烦就会减少。

有时,内核确实需要读取用户空间数据,而不仅仅是复制。该补丁集的第三个补丁包含了有助于实现此目的的函数。它引入了一个名为 `Validate` 的新特性(trait),该特性封装了在使用前验证用户空间数据的逻辑。对于自定义类型 `T`,`Validate<S>` 的实现包含了将 `Untrusted<S>` 转换为普通 `T` 所需的逻辑。不过,Lossin 对这个 API 并不完全满意,并希望找到时间进行改进。

Greg Kroah-Hartman 过去曾对标记来自用户空间输入数据这个想法充满热情,他要求 Lossin 增加一个使用 `Untrusted` 的驱动程序示例。

ioctl() 回调函数接收来自用户空间的不可信数据。这是我们遇到过无数次内核缓冲区溢出(buffer overflows)的地方之一,并且在使用数据之前,它_总是_需要进行适当的验证。

Lossin 尚未编写完整的示例驱动程序,但他分享了一个粗略的草图,展示了如何使用新的 API 来实现驱动程序的 `ioctl()` 函数。`ioctl()` 是一个棘手的接口,因为它接受两个参数:`cmd`,指定要运行的命令;以及 `arg`,其含义取决于 `cmd`。因此,`arg` 应该如何验证取决于用户空间发送了哪个命令。Lossin 建议在 Rust 中用一个枚举(enumeration)来表示这一点:

对 Rust 驱动 API 进行一些适当的调整后,通用的 `MiscDevice::ioctl()` 函数可以接受一个将 `cmd` 和 `arg` 捆绑在一起的结构体:

接着,`Untrusted<IoctlArgs>` 就可以被验证(validated)为一个正常的 `MyIoctlArgs` 值。当然,这并不能阻止程序员搞砸验证逻辑,但它会强制转换在一个特定的地方发生,并确保该转换被调用。希望这能让发现遗漏的检查变得更容易。

Kroah-Hartman 提出了一个问题:内核在数据仍然可被用户空间访问时对其进行“验证”,然后才使用 `copy_from_user()` 将其复制,这可能导致 bug。他想知道这个接口如何能防止这种情况。Lossin 解释说,示例中 `MyIoctlArgs` 中的 `UserPtr` 类型在这方面有所帮助。这些类型代表指向用户空间内存的指针;当与长度结合时,它们可以变成一个 `UserSlice`。`UserSlice::read_all()` 方法是 `copy_from_user()` 的 Rust 等价物。目前,`read_all()` 只是直接读取字节,但如果他的补丁集被接受,他会希望将其更改为将读取的字节标记为不可信:

按照 Lossin 勾勒出的类型,类型系统理论上会强制数据从 `Untrusted<IoctlArgs>` 进展到 `MyIoctlArgs`,再到 `UserPtr`(用户空间指针),然后到 `Untrusted<Vec<u8>>`(不可信字节向量),最终到适用于 `ioctl()` 参数的任何用户定义类型。由于他没有提供一个完整的、可运行的示例,他的回复仍然留下了一些未解答的问题,但 Kroah-Hartman 对此表示满意。截至撰写本文时,该补丁集尚未收到其他评论。如果该 API 以目前的形式被采纳,它将需要对 Rust 驱动程序的接口进行全面的更改,因此该补丁集很可能成为九月份即将举行的关于 Rust for Linux 项目的 Kangrejos 大会上的讨论主题。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值