KRF 是一个内核态随机错误注入器。
目前支持 Linux 和 FreeBSD 内核。
故障注入 是一种软件测试技术,通过在被测程序所调用的函数中诱发失败(“故障”)来工作。如果被调用方未能进行正确的错误检查和处理,这些故障可能导致不可靠的应用行为或可被利用的漏洞。
与现有的许多用户态故障注入系统不同,KRF 通过加载的内核模块在内核态运行。这有几个优点:
LD_PRELOAD 进行注入。syscall(3) 或内联汇编发起的调用。dlsym 更快速且更不容易出错。也有几个缺点:
cr0 寄存器。Linux 中可能有某种与架构无关的方法能做到这一点,但需要寻找。KRF 重写了 Linux 或 FreeBSD 的系统调用表:当通过 krfctl 配置后,KRF 会将可故障化的系统调用替换为轻量级包装函数。
每个包装函数随后执行一次检查,判断该调用是否应通过一个可配置的目标系统进行故障化,该系统可以针对特定的 personality(2)、PID、UID 和/或 GID。如果进程不应该被故障化,则调用原始系统调用。
最后,目标调用通过一个随机失败函数被故障化。例如,一个 read(2) 调用可能收到 EBADF、EINTR、EIO 等错误之一。
你可以在我们的博客文章中阅读更多关于 KRF 实现的内容。
注意:如果你有 Vagrant,请直接使用 Vagrantfile 并跳转到构建步骤。
KRF 应该能在任何较新的(4.15+)且启用了 CONFIG_KALLSYMS=1 的 Linux 内核上工作。
这包括 Ubuntu 18.04 的默认内核以及许多其他较新的发行版。
注意:如果你使用 Vagrant,请忽略此部分。
除了 C 工具链(Linux 可能需要 GCC)之外,KRF 唯一的依赖项应该是 libelf、内核头文件和 Ruby(>=2.4,用于代码生成)。
所有平台都需要 GNU Make;FreeBSD 额外需要 BSD Make。
对于使用 apt 的系统:
sudo apt install gcc make libelf-dev ruby linux-headers-$(uname -r)
git clone https://github.com/trailofbits/krf && cd krf
make -j$(nproc)
sudo make install # 安装模块到 /lib/modules,工具到 /usr/local/bin
sudo make insmod # 加载模块
或者,如果你使用 Vagrant:
git clone https://github.com/trailofbits/krf && cd krf
vagrant up linux && vagrant ssh linux
# 在虚拟机内
cd /vagrant
make -j$(nproc)
sudo make install # 安装模块到 /lib/modules,工具到 /usr/local/bin
sudo make insmod # 加载模块
或者,对于 FreeBSD:
git clone https://github.com/trailofbits/krf && cd krf
cd vagrant up freebsd && vagrant ssh freebsd
# 在虚拟机内
cd /vagrant
gmake # 不要用 make!
gmake install-module # 安装模块到 /boot/modules/
sudo gmake install-utils # 安装工具到 /usr/local/bin
gmake insmod # 加载模块
KRF 包含三个组件:
krfx)krfexec)krfctl)krfmesg)要加载内核模块,运行 make insmod。要卸载,运行 make rmmod。
首次使用时,可以在另一个终端中启动 sudo krfmesg 以查看 krfx 记录的消息。
KRF 初始处于中性状态:在用户通过 krfctl 指定行为之前,不会拦截或故障化任何系统调用:
# 无诱导故障,即使 KRF 已加载
ls
# 告诉 krf 故障化 read(2) 和 write(2) 调用
# 注意 krfctl 需要 root 权限
sudo krfctl -F 'read,write'
# 告诉 krf 故障化任何由 krfexec 启动的程序
# 这意味着 personality 为 28
sudo krfctl -T personality=28
# 可能会故障!
krfexec ls
# 告诉 krf 以 1/100(即 1%)的概率故障
# 注意此值以倒数表示
# 例如,1 表示所有可故障化的系统调用都会故障
# 500 表示平均每 500 个系统调用故障一次(1/500 即 0.2%)
sudo krfctl -p 100
# 告诉 krf 故障化 `io` 配置文件(即 I/O 相关的系统调用)
sudo krfctl -P io
# krfexec 也会正确传递选项
krfexec echo -n 'no newline'
# 清除故障规格
sudo krfctl -c
# 清除目标规格
sudo krfctl -C
# 无诱导故障,因为未故障化任何系统调用
krfexec firefox
注意:大多数用户应使用 krfctl 而非手动操作这些文件。
在 FreeBSD 中,这些相同的值可通过 sysctl krf.whatever 访问,而非 procfs。
/proc/krf/rng_state此文件允许用户读取和修改 KRF 的 PRNG 内部状态。
例如,以下每项都会正确更新状态:
echo "1234" | sudo tee /proc/krf/rng_state
echo "0777" | sudo tee /proc/krf/rng_state
echo "0xFF" | sudo tee /proc/krf/rng_state
状态是一个 32 位无符号整数;尝试将其修改为超出范围的值将失败。
/proc/krf/targeting此文件允许用户设置 KRF 用于系统调用目标化的值。
注意:KRF 默认使用一个当前 Linux 内核未使用的 personality 值。如果你更改它,应注意避免设置成 Linux 关心的值。man 2 personality 有详细信息。
echo "0 28" | sudo tee /proc/krf/targeting
Personality 为 28 是硬编码在 krfexec 中的,必须设置才能使由 krfexec 执行的事物被故障化。
/proc/krf/probability此文件允许用户读取和写入对给定(可故障化)系统调用诱导故障的概率。
概率以倒数表示,例如 1000 表示平均 0.1% 的可故障化系统调用会被故障化。
echo "100000" | sudo tee /proc/krf/probability
/proc/krf/control此文件控制 KRF 故障化的系统调用。
注意:大多数用户应使用 krfctl 而非直接与此文件交互——前者会自动执行系统调用名称到编号的转换,并在出现问题时提供更清晰的错误信息。
# 将槽位 0(通常为 SYS_read)的系统调用替换为其故障包装函数
echo "0" | sudo tee /proc/krf/control
传递任何大于 KRF_NR_SYSCALLS 的数字将使 KRF 刷新整个系统调用表,恢复到中性状态。由于对于任意版本的 Linux 内核,KRF_NR_SYSCALLS 可能不可预测,选择一个较大的数字(如 65535)也没问题。
传递一个没有故障注入包装函数的有效系统调用编号将导致对该文件的 write(2) 失败并返回 EOPNOTSUPP。
/proc/krf/log_faults此文件控制 KRF 是否在故障化的系统调用时发出内核日志。默认情况下,不发出任何日志消息。
注意:大多数用户应使用 krfctl 而非直接与此文件交互。
# 启用故障日志
echo "1" | sudo tee /proc/krf/log_faults
# 禁用故障日志
echo "0" | sudo tee /proc/krf/log_faults
# 读取日志状态
cat /proc/krf/log_faults
ENOMEM)。
personality(2) 值中添加更多位来实现。非常感谢 Andrew Reiter 完成了 KRF 到 FreeBSD 的初始移植。Andrew 的工作是在 Veracode 应用研究组进行的。
KRF 根据 GNU GPLv3 许可。
请参阅 LICENSE 文件了解确切条款。