rbpf虚拟机-验证器(verifier)
文章目录
- 一、概述
- 二、主要功能
- 三、关键函数解析
- `check_prog_len` - 程序长度检查
- `check_load_dw` - LD_DW指令检查
- `check_jmp_offset` - 跳转指令检查
- `check_registers` - 寄存器访问检查
- 四、操作码分类
- 五、总结
Welcome to Code Block's blog本篇文章主要介绍了
[rbpf虚拟机-验证器(verifier)]
❤博主广交技术好友,喜欢我的文章的可以关注一下❤
一、概述
该篇文章是rbpf虚拟机验证器代码块功能的整理。
(学习该虚拟机的目的是为了搞懂solana合约的执行方式,solana使用的rbpf是在该虚拟机上进行扩展。)
这个 eBPF 验证器是在 eBPF 程序加载到虚拟机(VM)时执行的简单检查工具,与 Linux 内核中更复杂的验证器不同。它不涉及程序流控制(应为有向无环图)或寄存器使用一致性等深入检查。
什么是rbpf虚拟机?
RBPF虚拟机是一种基于Rust语言开发的轻量级虚拟机,用于执行BPF(Berkeley Packet Filter)程序。BPF是一种在内核中执行过滤和转发网络数据包的技术,RBPF虚拟机可以用来模拟和执行这些BPF程序,以实现网络数据包的快速过滤和处理。RBPF虚拟机通过提供一个安全的执行环境,可以在用户空间中运行BPF程序,而无需直接访问内核,从而提高了安全性和性能。RBPF虚拟机还支持在不同的操作系统和平台上运行,是一个功能强大的工具,被广泛应用于网络安全、性能优化等领域。
二、主要功能
验证器主要执行以下检查:
-
程序长度检查
- 指令必须是 8 字节的倍数
- 最大指令数限制为 1,000,000 字节
- 程序不能为空
- 程序必须以 “EXIT” 指令结束
-
指令格式检查
- 检查 LD_DW(64位加载)指令是否完整(由两条连续指令组成)
- 检查跳转指令的目标是否有效
- 检查寄存器访问是否合法
-
操作码验证
- 验证所有操作码是否属于已知类型
三、关键函数解析
check_prog_len
- 程序长度检查
fn check_prog_len(prog: &[u8]) -> Result<(), Error> {
// 检查是否为8字节倍数
if prog.len() % ebpf::INSN_SIZE != 0 {
reject(...)?;
}
// 检查最大长度
if prog.len() > ebpf::PROG_MAX_SIZE {
reject(...)?;
}
// 检查非空
if prog.is_empty() {
reject(...)?;
}
// 检查必须以EXIT结束
let last_opc = ebpf::get_insn(prog, (prog.len() / ebpf::INSN_SIZE) - 1).opc;
if last_opc & ebpf::BPF_CLS_MASK != ebpf::BPF_JMP {
reject(...)?;
}
Ok(())
}
check_load_dw
- LD_DW指令检查
fn check_load_dw(prog: &[u8], insn_ptr: usize) -> Result<(), Error> {
// 获取下一条指令
let next_insn = ebpf::get_insn(prog, insn_ptr + 1);
// 下一条指令的操作码必须不为0
if next_insn.opc != 0 {
reject(...)?;
}
Ok(())
}
check_jmp_offset
- 跳转指令检查
fn check_jmp_offset(prog: &[u8], insn_ptr: usize) -> Result<(), Error> {
let insn = ebpf::get_insn(prog, insn_ptr);
// 检查死循环
if insn.off == -1 {
reject(...)?;
}
// 检查跳转目标是否在程序范围内
let dst_insn_ptr = insn_ptr as isize + 1 + insn.off as isize;
if dst_insn_ptr < 0 || dst_insn_ptr as usize >= (prog.len() / ebpf::INSN_SIZE) {
reject(...)?;
}
// 检查是否跳转到LD_DW指令的中间
let dst_insn = ebpf::get_insn(prog, dst_insn_ptr as usize);
if dst_insn.opc == 0 {
reject(...)?;
}
Ok(())
}
check_registers
- 寄存器访问检查
fn check_registers(insn: &ebpf::Insn, store: bool, insn_ptr: usize) -> Result<(), Error> {
// 源寄存器检查
if insn.src > 10 {
reject(...)?;
}
// 目标寄存器检查
match (insn.dst, store) {
(0..=9, _) | (10, true) => Ok(()), // R0-R9总是可写,R10仅在store=true时可写
(10, false) => reject(...), // 不可写入R10
(_, _) => reject(...), // 无效寄存器
}
}
四、操作码分类
验证器支持的操作码分为以下几类:
-
加载类 (BPF_LD)
- LD_ABS_B/H/W/DW - 固定偏移加载
- LD_IND_B/H/W/DW - 间接偏移加载
- LD_DW_IMM - 64位立即数加载(特殊处理)
-
存储类 (BPF_ST/BPF_STX)
- ST_B/H/W/DW_IMM - 立即数存储
- ST_B/H/W/DW_REG - 寄存器存储
-
算术逻辑运算类 (BPF_ALU/BPF_ALU64)
- 32位和64位的加减乘除、位操作等
-
跳转类 (BPF_JMP/BPF_JMP32)
- 条件跳转(等于、大于、小于等)
- 无条件跳转 (JA)
- 32位和64位版本
-
调用类 (BPF_CALL)
- 普通调用 (CALL)
- 尾调用 (TAIL_CALL)
五、总结
这个 eBPF 验证器虽然比 Linux 内核中的验证器简单,但它提供了基本的程序完整性检查:
- 确保程序格式正确
- 防止明显的安全风险(如无效内存访问)
- 验证指令序列的合法性
通过上述对源码进行解读,认识到验证器是虚拟机的门户,它保证了虚拟机的按照预定的规范的指令执行程序,保证了安全性和稳定性。
代码来源:rbpf虚拟机
鸣谢: qmonnet 提供的开源代码.
当然,我也会将带有中文注释和自己理解的一些代码上传的我的github页面,感兴趣的朋友可以进行clone查看.
我的GitHub:forked
感谢您的点赞、关注、收藏!