在修复一个由 freplace 引起的 tailcall 无限循环的问题时,接纳社区的建议:禁止将 freplace prog 更新到 prog_array map 中。

使用场景

在项目中,freplace prog 当作 tail-callee 的用法如下:

  1. dispatcher prog 中,调用 subprog1
  2. 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 能够 tailcallacl_algo prog,因为 acl_progsowner.typeBPF_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 可以 tailcallacl_algo prog,因为 acl_progsowner.typeBPF_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_progtype,所以 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 时,如果 progtypeBPF_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 仍然可以 tailcallfreplace prog,只要 freplace prog 的 typeBPF_PROG_TYPE_EXT