在修复一个由 freplace
引起的 tailcall
无限循环的问题时,接纳社区的建议:禁止将 freplace
prog 更新到 prog_array
map 中。
使用场景
在项目中,freplace
prog 当作 tail-callee
的用法如下:
在 dispatcher
prog 中,调用subprog1
;在 subprog1
中,如果是 acl 模块,则还需要通过tailcall
调用 acl algo prog。
// 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;
}
// 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
。
// 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
。
// 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 中
是这么实现的:
// 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
即可。
// 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
。