在修复一个由 freplace 引起的 tailcall 无限循环的问题时,接纳社区的建议:禁止将 freplace prog 更新到 prog_array map 中。
使用场景
在项目中,freplace prog 当作 tail-callee 的用法如下:
- 在 dispatcherprog 中,调用subprog1;
- 在 subprog1中,如果是 acl 模块,则还需要通过tailcall调用 acl algo prog。
|  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
 | // dispatcher.c
__noinline int
subprog1(struct xdp_md *xdp)
{
    volatile int retval = XDP_PASS;
    return retval;
}
__noinline int
subprog2(struct xdp_md *xdp)
{
    volatile int retval = XDP_PASS;
    return retval;
}
SEC("XDP")
int dispatcher(struct xdp_md *xdp)
{
    int retval;
    retval = subprog1(xdp);
    if (retval == XDP_PASS)
        retval = subprog2(xdp);
    return retval;
}
 | 
 
|  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
 | // acl.c
struct {
    __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
    __uint(max_entries, 2);
} acl_progs SEC(".maps");
static __noinline int
acl_algo(struct xdp_md *xdp, int prog_id)
{
    volatile int retval = XDP_PASS;
    bpf_tail_call(xdp, &acl_progs, prog_id);
    return retval;
}
SEC("freplace")
int acl(struct xdp_md *xdp)
{
    int prog_id = 1;
    return acl_algo(xdp, prog_id);
}
// acl_algo.c
SEC("freplace")
int acl_algo(struct xdp_md *xdp)
{
    volatile int retval = XDP_PASS;
    return retval;
}
 | 
 
在 5.15 内核里,acl prog 能够 tailcall 到 acl_algo prog,因为 acl_progs 的 owner.type 是 BPF_PROG_TYPE_EXT。
|  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
 | // https://github.com/torvalds/linux/blob/v5.15/kernel/bpf/core.c
bool bpf_prog_array_compatible(struct bpf_array *array,
                               const struct bpf_prog *fp)
{
    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;
}
 | 
 
在 6.6 内核里,acl prog 可以 tailcall 到 acl_algo prog,因为 acl_progs 的 owner.type 是 BPF_PROG_TYPE_XDP。
|  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
37
38
39
40
41
42
43
44
45
 | // https://github.com/torvalds/linux/blob/v6.6/kernel/bpf/core.c
bool bpf_prog_map_compatible(struct bpf_map *map,
                             const struct bpf_prog *fp)
{
    enum bpf_prog_type prog_type = resolve_prog_type(fp);
    bool ret;
    if (fp->kprobe_override)
        return false;
    /* XDP programs inserted into maps are not guaranteed to run on
     * a particular netdev (and can run outside driver context entirely
     * in the case of devmap and cpumap). Until device checks
     * are implemented, prohibit adding dev-bound programs to program maps.
     */
    if (bpf_prog_is_dev_bound(fp->aux))
        return false;
    spin_lock(&map->owner.lock);
    if (!map->owner.type) {
        /* There's no owner yet where we could check for
         * compatibility.
         */
        map->owner.type  = prog_type;
        map->owner.jited = fp->jited;
        map->owner.xdp_has_frags = fp->aux->xdp_has_frags;
        ret = true;
    } else {
        ret = map->owner.type  == prog_type &&
              map->owner.jited == fp->jited &&
              map->owner.xdp_has_frags == fp->aux->xdp_has_frags;
    }
    spin_unlock(&map->owner.lock);
    return ret;
}
// https://github.com/torvalds/linux/blob/v6.6/include/linux/bpf_verifier.h
static inline enum bpf_prog_type resolve_prog_type(const struct bpf_prog *prog)
{
    return prog->type == BPF_PROG_TYPE_EXT ?
           prog->aux->dst_prog->type : prog->type;
}
 | 
 
因为 resolve_prog_type() 会将 freplace prog 的 type 当作其 dst_prog 的 type,所以 acl prog 和 acl_algo prog 都被解析成 BPF_PROG_TYPE_XDP。
禁止将 freplace prog 更新到 prog_array map 中
是这么实现的:
|  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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
 | // https://github.com/kernel-patches/bpf/blob/bpf-next_base/kernel/bpf/arraymap.c
int bpf_fd_array_map_update_elem(struct bpf_map *map, struct file *map_file,
                                 void *key, void *value, u64 map_flags)
{
    struct bpf_array *array = container_of(map, struct bpf_array, map);
    void *new_ptr, *old_ptr;
    u32 index = *(u32 *)key, ufd;
    if (map_flags != BPF_ANY)
        return -EINVAL;
    if (index >= array->map.max_entries)
        return -E2BIG;
    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);
    if (map->ops->map_poke_run) {
        mutex_lock(&array->aux->poke_mutex);
        old_ptr = xchg(array->ptrs + index, new_ptr);
        map->ops->map_poke_run(map, index, old_ptr, new_ptr);
        mutex_unlock(&array->aux->poke_mutex);
    } else {
        old_ptr = xchg(array->ptrs + index, new_ptr);
    }
    if (old_ptr)
        map->ops->map_fd_put_ptr(map, old_ptr, true);
    return 0;
}
static void *prog_fd_array_get_ptr(struct bpf_map *map,
                                   struct file *map_file, int fd)
{
    struct bpf_prog *prog = bpf_prog_get(fd);
    bool is_extended;
    if (IS_ERR(prog))
        return prog;
    if (prog->type == BPF_PROG_TYPE_EXT ||
        !bpf_prog_map_compatible(map, prog)) {
        bpf_prog_put(prog);
        return ERR_PTR(-EINVAL);
    }
    // ...
    return prog;
}
 | 
 
这是在往 prog_array map 中更新 prog 、从 fd 获取 prog 时,如果 prog 的 type 是 BPF_PROG_TYPE_EXT,就返回 -EINVAL。
绕过该限制
想要绕过该限制,比较简单,将 acl_algo prog 的 type 改成 BPF_PROG_TYPE_XDP 即可。
| 1
2
3
4
5
6
7
8
9
 | // acl_algo.c
SEC("XDP")
int acl_algo(struct xdp_md *xdp)
{
    volatile int retval = XDP_PASS;
    return retval;
}
 | 
 
因为 bpf_prog_map_compatible() 里调用 resolve_prog_type() 时,acl prog 的 type 会被解析成 BPF_PROG_TYPE_XDP。
总结
预计 6.12 内核里就会有这个限制,所以在 freplace prog 中使用 tailcall 时,需要注意这个限制。
而在 6.12 内核之前,freplace prog 仍然可以 tailcall 到 freplace prog,只要 freplace prog 的 type 是 BPF_PROG_TYPE_EXT。