处理实参
系统调用应按用户空间提供的顺序处理实参。例如,应按参数提供给系统调用的顺序验证参数。如果不同的系统调用针对不同的验证错误产生不同的 Errno 值,验证顺序就显得尤为重要,因为如果实参存在多种无效方式,我们需要向用户空间返回正确的 Errno 值。
如果系统调用实参需要读取用户空间内存(例如,如果实参是 UserAddress 或 UserRef),则系统调用应按实参的提供顺序使用实参读取用户空间内存。我们从用户空间内存读取的顺序对用户空间可见,因为这些读取操作可能会生成故障,而这些故障会报告给用户空间。
错误处理
用户空间不应能够使用系统调用来使 Starnix 内核崩溃。例如,系统调用不应使用会发生 panic 的 Rust API(例如,unwrap 或 expect)时,它们会遇到无效或意外的参数。
标志或选项
许多系统调用都采用位字段实参(通常为 u32),其中包含系统调用的标志或选项。一种良好的做法是在对实参进行更详细的处理之前,通过检查实参是否包含未知位来验证此位字段。
示例:
if flags & !(AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW) != 0 {
track_stub!(...)
return error!(EINVAL);
}
使用 track_stub 宏跟踪未知标志。跟踪不受支持的功能有助于调试用户空间代码的人员注意到,他们看到的问题可能是由 Starnix 中缺少的功能引起的。
Starnix 通常使用 bitflags 宏来定义作为实参传递给系统调用的位域的 Starnix 内部类型。在大多数情况下,系统调用应使用 from_bits 方法将原始系统调用实参转换为 Starnix 内部类型,因为 from_bits 可让调用方显式处理原始值包含未知位的情况。
对用户空间提供的值执行算术运算
系统调用应避免对来自用户空间的值使用可能导致数值溢出的算术运算。例如,如果系统调用从用户空间接收到两个数值实参,那么在不进行验证的情况下添加这些值可能会导致数值溢出(如果用户空间提供的值过大)。在 Rust 中,数值溢出会导致 panic,这不是处理无效实参的正确方式。
请改为验证数值的范围,或使用 checked_add 等函数,让调用代码显式处理溢出。
考虑使用 UserValue 而不是原始数值类型,以便更轻松地验证来自用户空间的值。
用户地址
使用用户空间地址时,请勿使用原始数值或原始指针。请改为使用 UserAddress 类型表示地址。如果地址是指向用户空间中特定结构体的指针,请使用 UserRef 类型,该类型将特定结构体作为类型形参。如果地址是指向在不同架构上具有不同布局的结构的指针,请使用 MultiArchUserRef 类型。UserRef 和 MultiArchUserRef 类型对于处理对象数组也很有用。
系统调用不应从用户空间内存中读取同一位置多次,因为用户空间中的另一个线程可能会在系统调用执行期间修改用户空间内存。而是从用户空间复制到系统调用实现中的变量。
写入用户空间内存的系统调用需要谨慎,因为用户空间可能会提供相互重叠的地址。因此,系统调用不应交错读取和写入用户空间内存。相反,系统调用应在执行写入操作之前从用户空间执行所有读取操作。