eBPF 可以修改系统调用的 return 值或参数吗?

Can eBPF modify the return value or parameters of a syscall?

为了模拟某些行为,我想将探测器附加到系统调用并在传递某些参数时修改 return 值。或者,在函数的参数被处理之前修改它也足够了。

这可以用 BPF 实现吗?

我相信将 eBPF 附加到 kprobes/kretprobes 可以让您读取函数参数和 return 值,但您不能篡改它们。我不是 100% 确定;要求确认的好地方是 IO Visor 项目 mailing list 或 IRC 频道(#iovisor at irc.oftc.net)。

作为替代解决方案,我知道您至少可以使用 strace, with the -e option. Quoting the manual page:

更改系统调用的 return 值
-e inject=set[:error=errno|:retval=value][:signal=sig][:when=expr]
       Perform syscall tampering for the specified set of syscalls.

此外,在 Fosdem 2017 上有关于此的 a presentation 和故障注入,如果您对此感兴趣的话。这是幻灯片中的一个示例命令:

strace -P precious.txt -efault=unlink:retval=0 unlink precious.txt

编辑: 正如 Ben 所说,kprobes 和跟踪点上的 eBPF 绝对是只读的,用于跟踪和监控用例。我也在 IRC 上得到了确认。

在内核探测 (kprobes) 中,eBPF 虚拟机对系统调用参数和 return 值具有只读访问权限。

然而,eBPF 程序将有自己的 return 代码。可以应用捕获 BPF 的 seccomp 配置文件(不是 eBPF;感谢@qeole)return 代码并在执行期间中断系统调用。

允许的运行时修改是:

  • SECCOMP_RET_KILL: 用SIGSYS
  • 立即杀死
  • SECCOMP_RET_TRAP:发送一个可捕捉的SIGSYS,给模拟系统调用的机会
  • SECCOMP_RET_ERRNO:强制errno
  • SECCOMP_RET_TRACE: 将决定让给 ptracer 或将 errno 设置为 -ENOSYS
  • SECCOMP_RET_ALLOW: 允许

https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt

SECCOMP_RET_TRACE 方法可以修改执行的系统调用、参数或 return 值。这是依赖于体系结构的,强制外部引用的修改可能会导致 ENOSYS 错误。

它通过将执行传递给等待的用户空间 ptrace 来实现,它能够修改跟踪的进程内存、寄存器和文件描述符。

跟踪器需要先调用ptrace,然后再调用waitpid。一个例子:

ptrace(PTRACE_SETOPTIONS, tracee_pid, 0, PTRACE_O_TRACESECCOMP);
waitpid(tracee_pid, &status, 0);

http://man7.org/linux/man-pages/man2/ptrace.2.html

waitpid return 时,根据 status 的内容,可以使用 PTRACE_GETEVENTMSG ptrace 操作检索 seccomp return 值。这将检索 seccomp SECCOMP_RET_DATA 值,这是一个由 BPF 程序设置的 16 位字段。示例:

ptrace(PTRACE_GETEVENTMSG, tracee_pid, 0, &data);

可以在继续操作之前在内存中修改系统调用参数。您可以使用 PTRACE_SYSCALL 步骤执行单个系统调用进入或退出。 Syscall return 值可以在恢复执行之前在用户空间中修改;底层程序将无法看到系统调用 return 值已被修改。

示例实现: Filter and Modify System Calls with seccomp and ptrace

可以使用 eBPF 修改一些用户 space 内存。如 bpf.h header file 中所述:

 * int bpf_probe_write_user(void *dst, const void *src, u32 len)
 *  Description
 *      Attempt in a safe way to write *len* bytes from the buffer
 *      *src* to *dst* in memory. It only works for threads that are in
 *      user context, and *dst* must be a valid user space address.
 *
 *      This helper should not be used to implement any kind of
 *      security mechanism because of TOC-TOU attacks, but rather to
 *      debug, divert, and manipulate execution of semi-cooperative
 *      processes.
 *
 *      Keep in mind that this feature is meant for experiments, and it
 *      has a risk of crashing the system and running programs.
 *      Therefore, when an eBPF program using this helper is attached,
 *      a warning including PID and process name is printed to kernel
 *      logs.
 *  Return
 *      0 on success, or a negative error in case of failure.

此外,引用自the BPF design Q&A

Tracing BPF programs can overwrite the user memory of the current task with bpf_probe_write_user(). Every time such program is loaded the kernel will print warning message, so this helper is only useful for experiments and prototypes. Tracing BPF programs are root only.

您的 eBPF 可能会将数据写入用户 space 内存位置。请注意,您仍然不能从您的 eBPF 程序中修改内核结构。

可以使用 eBPF 将错误注入系统调用:https://lwn.net/Articles/740146/

有一个名为 bpf_override_return() 的 bpf 函数,它可以覆盖调用的 return 值。这是一个使用 bcc 作为前端的例子:https://github.com/iovisor/bcc/blob/master/tools/inject.py

根据 Linux manual page:

bpf_override_return() is only available if the kernel was compiled with the CONFIG_BPF_KPROBE_OVERRIDE configuration option, and in this case it only works on functions tagged with ALLOW_ERROR_INJECTION in the kernel code.

Also, the helper is only available for the architectures having the CONFIG_FUNCTION_ERROR_INJECTION option. As of this writing, x86 architecture is the only one to support this feature.

可以向错误注入框架添加一个函数。可以在此处找到更多信息:https://github.com/iovisor/bcc/issues/2485