在没有分支操作码的情况下测试一个整数与 eBPF 中的其他两个整数不同

pat*_*lea 5 assembly clang linux-kernel bpf ebpf

我正在编写一个检查任务 UID 的 eBPF kprobe,即在 execve 调用之间唯一允许的 UID 更改是 setuid()、seteuid() 和 setreuid() 调用所允许的 UID 更改。

由于探测器检查所有任务,因此它使用从 init_task 开始迭代的展开循环,并且最多必须使用 1024 或 8192 个分支,具体取决于内核版本。

我的问题是,如果存在非法更改,如何实现返回非零的检查,定义为:

(new_ruid != old_euid && new_ruid != old_ruid) ||
(new_euid != old_euid && new_euid != old_ruid && new_euid != old_suid)
Run Code Online (Sandbox Code Playgroud)

但不使用分支(clang 使用跳转来短路检查之间的任何表达式是否&&计算为 true)。

Mar*_*lli 6

您应该能够使用按位或、异或、移位和整数乘法来完成此操作。我假设你的变量都是__s32or __u32,在继续之前将它们转换为__u64以避免出现问题(否则将下面乘法的每个操作数转换为__u64)。

明明a != b可以成为a ^ b。有点&&棘手,但可以转换为乘法(其中如果任何操作数是,则0结果是0)。那么你的条件的第一部分就变成了:

// (new_ruid != old_euid && new_ruid != old_ruid)
__u64 x = (new_ruid ^ old_euid) * (new_ruid ^ old_euid);
Run Code Online (Sandbox Code Playgroud)

然而,对于第二部分,我们遇到了溢出问题,因为有 3 个条件。您可以通过将前两个结果“压缩”到低 32 位来避免这种情况,因为您并不真正关心乘法,只关心它的“真实性”:

// (new_euid != old_euid && new_euid != old_ruid && new_euid != old_suid)

__u64 y = (new_euid ^ old_euid) * (new_euid ^ old_ruid);
y = (y >> 32) | (y & 0xffffffff);
y *= (new_euid ^ old_suid);
Run Code Online (Sandbox Code Playgroud)

最后将两部分进行“或”运算即可得到结果。如果您想要的话,您还可以再次“压缩”到较低的 32 位__u32

__u64 res = x | y;
// or
__u64 tmp = x | y;
__u32 res = (tmp >> 32) | (tmp & 0xffffffff);
Run Code Online (Sandbox Code Playgroud)

无论优化级别如何,上述所有组合编译对我来说都没有任何分支。