tailcall in freplace 有 BUG?真的?假的!

最近,偶然发现在 freplace 里使用 tailcall 有 BUG。尽管,我的同事早就发现了有问题,但不确定是不是内核的问题。所以,我就花了点时间去确认了一下出错的地方。

BUG 分析

当给 freplace 里的 PROG_ARRAY bpf map 更新表项时,会触发 BUG:

1
2
# ./tailcall-in-freplace
2023/08/27 15:00:28 Failed to put tailcall: update: invalid argument

看看内核源代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// KERNEL version:5.15
__sys_bpf()                                     // ${KERNEL}/kernel/bpf/syscall.c
|-->map_update_elem()
    |-->bpf_map_update_value()
        |-->bpf_fd_array_map_update_elem() {    // ${KERNEL}/kernel/bpf/arraymap.c
                ufd = *(u32 *)value;
                new_ptr = map->ops->map_fd_get_ptr(map, map_file, ufd);
                if (IS_ERR(new_ptr))
                    return PTR_ERR(new_ptr);
            }
           /
          /
         /
        |-->prog_fd_array_get_ptr()             // ${KERNEL}/kernel/bpf/arraymap.c
            |-->bpf_prog_array_compatible() {   // ${KERNEL}/kernel/bpf/core.c
                    bool ret;

                    if (fp->kprobe_override)
                        return false;

                    spin_lock(&array->aux->owner.lock);

                    if (!array->aux->owner.type) {
                        /* There's no owner yet where we could check for
                        * compatibility.
                        */
                        array->aux->owner.type  = fp->type;
                        array->aux->owner.jited = fp->jited;
                        ret = true;
                    } else {
                        ret = array->aux->owner.type  == fp->type &&
                            array->aux->owner.jited == fp->jited;
                    }
                    spin_unlock(&array->aux->owner.lock);
                    return ret;
                }

怀疑是 bpf_prog_array_compatible() 检查失败了,怎么办?

retsnoop trace 一下看看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# retsnoop -e '*sys_bpf' -a 'bpf_fd_array_*' -a 'bpf_prog_array_compatible' -T
Receiving data...
15:22:51.303186 -> 15:22:51.303245 TID/PID 5275/5273 (tailcall-in-fre/tailcall-in-fre):

FUNCTION CALL TRACE                       RESULT     DURATION
---------------------------------------   ---------  --------
→ __x64_sys_bpf
    → __sys_bpf
        → bpf_fd_array_map_update_elem
            ↔ bpf_prog_array_compatible   [false]     0.594us
        ← bpf_fd_array_map_update_elem    [-EINVAL]   1.889us
    ← __sys_bpf                           [-EINVAL]   7.433us
← __x64_sys_bpf                           [-EINVAL]   8.730us

                    entry_SYSCALL_64_after_hwframe+0x61  (arch/x86/entry/entry_64.S:118:0)
                    __kretprobe_trampoline+0x0
                    __kretprobe_trampoline+0x0
                    __sys_bpf+0x88c                      (kernel/bpf/syscall.c:4602:9)
                    map_update_elem+0x1af                (kernel/bpf/syscall.c:1163:8)
                    __kretprobe_trampoline+0x0
!    8us [-EINVAL]  __x64_sys_bpf
!    7us [-EINVAL]  __sys_bpf
!    1us [-EINVAL]  bpf_fd_array_map_update_elem

噢,果真是 bpf_prog_array_compatible() 检查失败了。

再来用 drgn-bpf 看看 array->aux->owner.typ 是什么。

1
2
3
4
5
6
# drgn tools/bpf_inspect.py m -D
    58: BPF_MAP_TYPE_PERF_EVENT_ARRAY    events
    59: BPF_MAP_TYPE_PERCPU_ARRAY        socks
    60: BPF_MAP_TYPE_ARRAY               .rodata
    61: BPF_MAP_TYPE_PROG_ARRAY          progs
        owner bpf prog: BPF_PROG_TYPE_EXT jited:True

果真,owner.type 有问题,是 BPF_PROG_TYPE_EXT,而不是预期的 BPF_PROG_TYPE_KPROBE

而在 6.5.0-rc4+ 内核里:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# drgn tools/bpf_inspect.py m -D
   131: BPF_MAP_TYPE_ARRAY               .rodata
   132: BPF_MAP_TYPE_PERCPU_ARRAY        socks
   133: BPF_MAP_TYPE_PERF_EVENT_ARRAY    events
   134: BPF_MAP_TYPE_PROG_ARRAY          progs
        owner bpf prog: BPF_PROG_TYPE_KPROBE jited:True
        progs[0]: BPF_PROG_TYPE_KPROBE handle_new_connection bpf_prog_12e9e2b2c252d64a_handle_new_connection 0xffffffffc00fb63c
        progs[1]: BPF_PROG_TYPE_KPROBE handle_new_connection bpf_prog_12e9e2b2c252d64a_handle_new_connection 0xffffffffc00fb63c
        progs[2]: BPF_PROG_TYPE_KPROBE handle_new_connection bpf_prog_12e9e2b2c252d64a_handle_new_connection 0xffffffffc00fb63c
        progs[3]: BPF_PROG_TYPE_KPROBE handle_new_connection bpf_prog_12e9e2b2c252d64a_handle_new_connection 0xffffffffc00fb63c
        progs[4]: BPF_PROG_TYPE_KPROBE handle_new_connection bpf_prog_12e9e2b2c252d64a_handle_new_connection 0xffffffffc00fb63c
        progs[5]: BPF_PROG_TYPE_KPROBE handle_new_connection bpf_prog_12e9e2b2c252d64a_handle_new_connection 0xffffffffc00fb63c
        progs[6]: BPF_PROG_TYPE_KPROBE handle_new_connection bpf_prog_12e9e2b2c252d64a_handle_new_connection 0xffffffffc00fb63c
        progs[7]: BPF_PROG_TYPE_KPROBE handle_new_connection bpf_prog_12e9e2b2c252d64a_handle_new_connection 0xffffffffc00fb63c
        progs[8]: BPF_PROG_TYPE_KPROBE handle_new_connection bpf_prog_12e9e2b2c252d64a_handle_new_connection 0xffffffffc00fb63c
        progs[9]: BPF_PROG_TYPE_KPROBE handle_new_connection bpf_prog_12e9e2b2c252d64a_handle_new_connection 0xffffffffc00fb63c
   135: BPF_MAP_TYPE_ARRAY               .rodata

tailcall in freplace demo 源代码:GitHub - Asphaltt/tailcall-in-freplace

BUG 修复

最终,经过一番研究,发现该 BUG 在 6.2 内核里修复了:

一声叹息,就这么失去一个给内核提交 patch 的机会。

小结

一番折腾,弄了个 eBPF 加强版的 drgn:drgn-bpf,欢迎 star。