bpf: Prevent tailcall infinite loop caused by freplace
[ Upstream commit d6083f040d5d8f8d748462c77e90547097df936e ] There is a potential infinite loop issue that can occur when using a combination of tail calls and freplace. In an upcoming selftest, the attach target for entry_freplace of tailcall_freplace.c is subprog_tc of tc_bpf2bpf.c, while the tail call in entry_freplace leads to entry_tc. This results in an infinite loop: entry_tc -> subprog_tc -> entry_freplace --tailcall-> entry_tc. The problem arises because the tail_call_cnt in entry_freplace resets to zero each time entry_freplace is executed, causing the tail call mechanism to never terminate, eventually leading to a kernel panic. To fix this issue, the solution is twofold: 1. Prevent updating a program extended by an freplace program to a prog_array map. 2. Prevent extending a program that is already part of a prog_array map with an freplace program. This ensures that: * If a program or its subprogram has been extended by an freplace program, it can no longer be updated to a prog_array map. * If a program has been added to a prog_array map, neither it nor its subprograms can be extended by an freplace program. Moreover, an extension program should not be tailcalled. As such, return -EINVAL if the program has a type of BPF_PROG_TYPE_EXT when adding it to a prog_array map. Additionally, fix a minor code style issue by replacing eight spaces with a tab for proper formatting. Reviewed-by: Eduard Zingerman <eddyz87@gmail.com> Signed-off-by: Leon Hwang <leon.hwang@linux.dev> Link: https://lore.kernel.org/r/20241015150207.70264-2-leon.hwang@linux.dev Signed-off-by: Alexei Starovoitov <ast@kernel.org> Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
5b6209c793
commit
987aa730ba
+13
-4
@@ -1300,8 +1300,12 @@ void *__bpf_dynptr_data_rw(const struct bpf_dynptr_kern *ptr, u32 len);
|
|||||||
bool __bpf_dynptr_is_rdonly(const struct bpf_dynptr_kern *ptr);
|
bool __bpf_dynptr_is_rdonly(const struct bpf_dynptr_kern *ptr);
|
||||||
|
|
||||||
#ifdef CONFIG_BPF_JIT
|
#ifdef CONFIG_BPF_JIT
|
||||||
int bpf_trampoline_link_prog(struct bpf_tramp_link *link, struct bpf_trampoline *tr);
|
int bpf_trampoline_link_prog(struct bpf_tramp_link *link,
|
||||||
int bpf_trampoline_unlink_prog(struct bpf_tramp_link *link, struct bpf_trampoline *tr);
|
struct bpf_trampoline *tr,
|
||||||
|
struct bpf_prog *tgt_prog);
|
||||||
|
int bpf_trampoline_unlink_prog(struct bpf_tramp_link *link,
|
||||||
|
struct bpf_trampoline *tr,
|
||||||
|
struct bpf_prog *tgt_prog);
|
||||||
struct bpf_trampoline *bpf_trampoline_get(u64 key,
|
struct bpf_trampoline *bpf_trampoline_get(u64 key,
|
||||||
struct bpf_attach_target_info *tgt_info);
|
struct bpf_attach_target_info *tgt_info);
|
||||||
void bpf_trampoline_put(struct bpf_trampoline *tr);
|
void bpf_trampoline_put(struct bpf_trampoline *tr);
|
||||||
@@ -1383,12 +1387,14 @@ void bpf_jit_uncharge_modmem(u32 size);
|
|||||||
bool bpf_prog_has_trampoline(const struct bpf_prog *prog);
|
bool bpf_prog_has_trampoline(const struct bpf_prog *prog);
|
||||||
#else
|
#else
|
||||||
static inline int bpf_trampoline_link_prog(struct bpf_tramp_link *link,
|
static inline int bpf_trampoline_link_prog(struct bpf_tramp_link *link,
|
||||||
struct bpf_trampoline *tr)
|
struct bpf_trampoline *tr,
|
||||||
|
struct bpf_prog *tgt_prog)
|
||||||
{
|
{
|
||||||
return -ENOTSUPP;
|
return -ENOTSUPP;
|
||||||
}
|
}
|
||||||
static inline int bpf_trampoline_unlink_prog(struct bpf_tramp_link *link,
|
static inline int bpf_trampoline_unlink_prog(struct bpf_tramp_link *link,
|
||||||
struct bpf_trampoline *tr)
|
struct bpf_trampoline *tr,
|
||||||
|
struct bpf_prog *tgt_prog)
|
||||||
{
|
{
|
||||||
return -ENOTSUPP;
|
return -ENOTSUPP;
|
||||||
}
|
}
|
||||||
@@ -1492,6 +1498,9 @@ struct bpf_prog_aux {
|
|||||||
bool xdp_has_frags;
|
bool xdp_has_frags;
|
||||||
bool exception_cb;
|
bool exception_cb;
|
||||||
bool exception_boundary;
|
bool exception_boundary;
|
||||||
|
bool is_extended; /* true if extended by freplace program */
|
||||||
|
u64 prog_array_member_cnt; /* counts how many times as member of prog_array */
|
||||||
|
struct mutex ext_mutex; /* mutex for is_extended and prog_array_member_cnt */
|
||||||
struct bpf_arena *arena;
|
struct bpf_arena *arena;
|
||||||
/* BTF_KIND_FUNC_PROTO for valid attach_btf_id */
|
/* BTF_KIND_FUNC_PROTO for valid attach_btf_id */
|
||||||
const struct btf_type *attach_func_proto;
|
const struct btf_type *attach_func_proto;
|
||||||
|
|||||||
+24
-2
@@ -947,22 +947,44 @@ static void *prog_fd_array_get_ptr(struct bpf_map *map,
|
|||||||
struct file *map_file, int fd)
|
struct file *map_file, int fd)
|
||||||
{
|
{
|
||||||
struct bpf_prog *prog = bpf_prog_get(fd);
|
struct bpf_prog *prog = bpf_prog_get(fd);
|
||||||
|
bool is_extended;
|
||||||
|
|
||||||
if (IS_ERR(prog))
|
if (IS_ERR(prog))
|
||||||
return prog;
|
return prog;
|
||||||
|
|
||||||
if (!bpf_prog_map_compatible(map, prog)) {
|
if (prog->type == BPF_PROG_TYPE_EXT ||
|
||||||
|
!bpf_prog_map_compatible(map, prog)) {
|
||||||
bpf_prog_put(prog);
|
bpf_prog_put(prog);
|
||||||
return ERR_PTR(-EINVAL);
|
return ERR_PTR(-EINVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutex_lock(&prog->aux->ext_mutex);
|
||||||
|
is_extended = prog->aux->is_extended;
|
||||||
|
if (!is_extended)
|
||||||
|
prog->aux->prog_array_member_cnt++;
|
||||||
|
mutex_unlock(&prog->aux->ext_mutex);
|
||||||
|
if (is_extended) {
|
||||||
|
/* Extended prog can not be tail callee. It's to prevent a
|
||||||
|
* potential infinite loop like:
|
||||||
|
* tail callee prog entry -> tail callee prog subprog ->
|
||||||
|
* freplace prog entry --tailcall-> tail callee prog entry.
|
||||||
|
*/
|
||||||
|
bpf_prog_put(prog);
|
||||||
|
return ERR_PTR(-EBUSY);
|
||||||
|
}
|
||||||
|
|
||||||
return prog;
|
return prog;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void prog_fd_array_put_ptr(struct bpf_map *map, void *ptr, bool need_defer)
|
static void prog_fd_array_put_ptr(struct bpf_map *map, void *ptr, bool need_defer)
|
||||||
{
|
{
|
||||||
|
struct bpf_prog *prog = ptr;
|
||||||
|
|
||||||
|
mutex_lock(&prog->aux->ext_mutex);
|
||||||
|
prog->aux->prog_array_member_cnt--;
|
||||||
|
mutex_unlock(&prog->aux->ext_mutex);
|
||||||
/* bpf_prog is freed after one RCU or tasks trace grace period */
|
/* bpf_prog is freed after one RCU or tasks trace grace period */
|
||||||
bpf_prog_put(ptr);
|
bpf_prog_put(prog);
|
||||||
}
|
}
|
||||||
|
|
||||||
static u32 prog_fd_array_sys_lookup_elem(void *ptr)
|
static u32 prog_fd_array_sys_lookup_elem(void *ptr)
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ struct bpf_prog *bpf_prog_alloc_no_stats(unsigned int size, gfp_t gfp_extra_flag
|
|||||||
INIT_LIST_HEAD_RCU(&fp->aux->ksym_prefix.lnode);
|
INIT_LIST_HEAD_RCU(&fp->aux->ksym_prefix.lnode);
|
||||||
#endif
|
#endif
|
||||||
mutex_init(&fp->aux->used_maps_mutex);
|
mutex_init(&fp->aux->used_maps_mutex);
|
||||||
|
mutex_init(&fp->aux->ext_mutex);
|
||||||
mutex_init(&fp->aux->dst_mutex);
|
mutex_init(&fp->aux->dst_mutex);
|
||||||
|
|
||||||
return fp;
|
return fp;
|
||||||
|
|||||||
@@ -3218,7 +3218,8 @@ static void bpf_tracing_link_release(struct bpf_link *link)
|
|||||||
container_of(link, struct bpf_tracing_link, link.link);
|
container_of(link, struct bpf_tracing_link, link.link);
|
||||||
|
|
||||||
WARN_ON_ONCE(bpf_trampoline_unlink_prog(&tr_link->link,
|
WARN_ON_ONCE(bpf_trampoline_unlink_prog(&tr_link->link,
|
||||||
tr_link->trampoline));
|
tr_link->trampoline,
|
||||||
|
tr_link->tgt_prog));
|
||||||
|
|
||||||
bpf_trampoline_put(tr_link->trampoline);
|
bpf_trampoline_put(tr_link->trampoline);
|
||||||
|
|
||||||
@@ -3358,7 +3359,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,
|
|||||||
* in prog->aux
|
* in prog->aux
|
||||||
*
|
*
|
||||||
* - if prog->aux->dst_trampoline is NULL, the program has already been
|
* - if prog->aux->dst_trampoline is NULL, the program has already been
|
||||||
* attached to a target and its initial target was cleared (below)
|
* attached to a target and its initial target was cleared (below)
|
||||||
*
|
*
|
||||||
* - if tgt_prog != NULL, the caller specified tgt_prog_fd +
|
* - if tgt_prog != NULL, the caller specified tgt_prog_fd +
|
||||||
* target_btf_id using the link_create API.
|
* target_btf_id using the link_create API.
|
||||||
@@ -3433,7 +3434,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,
|
|||||||
if (err)
|
if (err)
|
||||||
goto out_unlock;
|
goto out_unlock;
|
||||||
|
|
||||||
err = bpf_trampoline_link_prog(&link->link, tr);
|
err = bpf_trampoline_link_prog(&link->link, tr, tgt_prog);
|
||||||
if (err) {
|
if (err) {
|
||||||
bpf_link_cleanup(&link_primer);
|
bpf_link_cleanup(&link_primer);
|
||||||
link = NULL;
|
link = NULL;
|
||||||
|
|||||||
+39
-8
@@ -528,7 +528,27 @@ static enum bpf_tramp_prog_type bpf_attach_type_to_tramp(struct bpf_prog *prog)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __bpf_trampoline_link_prog(struct bpf_tramp_link *link, struct bpf_trampoline *tr)
|
static int bpf_freplace_check_tgt_prog(struct bpf_prog *tgt_prog)
|
||||||
|
{
|
||||||
|
struct bpf_prog_aux *aux = tgt_prog->aux;
|
||||||
|
|
||||||
|
guard(mutex)(&aux->ext_mutex);
|
||||||
|
if (aux->prog_array_member_cnt)
|
||||||
|
/* Program extensions can not extend target prog when the target
|
||||||
|
* prog has been updated to any prog_array map as tail callee.
|
||||||
|
* It's to prevent a potential infinite loop like:
|
||||||
|
* tgt prog entry -> tgt prog subprog -> freplace prog entry
|
||||||
|
* --tailcall-> tgt prog entry.
|
||||||
|
*/
|
||||||
|
return -EBUSY;
|
||||||
|
|
||||||
|
aux->is_extended = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __bpf_trampoline_link_prog(struct bpf_tramp_link *link,
|
||||||
|
struct bpf_trampoline *tr,
|
||||||
|
struct bpf_prog *tgt_prog)
|
||||||
{
|
{
|
||||||
enum bpf_tramp_prog_type kind;
|
enum bpf_tramp_prog_type kind;
|
||||||
struct bpf_tramp_link *link_exiting;
|
struct bpf_tramp_link *link_exiting;
|
||||||
@@ -549,6 +569,9 @@ static int __bpf_trampoline_link_prog(struct bpf_tramp_link *link, struct bpf_tr
|
|||||||
/* Cannot attach extension if fentry/fexit are in use. */
|
/* Cannot attach extension if fentry/fexit are in use. */
|
||||||
if (cnt)
|
if (cnt)
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
|
err = bpf_freplace_check_tgt_prog(tgt_prog);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
tr->extension_prog = link->link.prog;
|
tr->extension_prog = link->link.prog;
|
||||||
return bpf_arch_text_poke(tr->func.addr, BPF_MOD_JUMP, NULL,
|
return bpf_arch_text_poke(tr->func.addr, BPF_MOD_JUMP, NULL,
|
||||||
link->link.prog->bpf_func);
|
link->link.prog->bpf_func);
|
||||||
@@ -575,17 +598,21 @@ static int __bpf_trampoline_link_prog(struct bpf_tramp_link *link, struct bpf_tr
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
int bpf_trampoline_link_prog(struct bpf_tramp_link *link, struct bpf_trampoline *tr)
|
int bpf_trampoline_link_prog(struct bpf_tramp_link *link,
|
||||||
|
struct bpf_trampoline *tr,
|
||||||
|
struct bpf_prog *tgt_prog)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
mutex_lock(&tr->mutex);
|
mutex_lock(&tr->mutex);
|
||||||
err = __bpf_trampoline_link_prog(link, tr);
|
err = __bpf_trampoline_link_prog(link, tr, tgt_prog);
|
||||||
mutex_unlock(&tr->mutex);
|
mutex_unlock(&tr->mutex);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __bpf_trampoline_unlink_prog(struct bpf_tramp_link *link, struct bpf_trampoline *tr)
|
static int __bpf_trampoline_unlink_prog(struct bpf_tramp_link *link,
|
||||||
|
struct bpf_trampoline *tr,
|
||||||
|
struct bpf_prog *tgt_prog)
|
||||||
{
|
{
|
||||||
enum bpf_tramp_prog_type kind;
|
enum bpf_tramp_prog_type kind;
|
||||||
int err;
|
int err;
|
||||||
@@ -596,6 +623,8 @@ static int __bpf_trampoline_unlink_prog(struct bpf_tramp_link *link, struct bpf_
|
|||||||
err = bpf_arch_text_poke(tr->func.addr, BPF_MOD_JUMP,
|
err = bpf_arch_text_poke(tr->func.addr, BPF_MOD_JUMP,
|
||||||
tr->extension_prog->bpf_func, NULL);
|
tr->extension_prog->bpf_func, NULL);
|
||||||
tr->extension_prog = NULL;
|
tr->extension_prog = NULL;
|
||||||
|
guard(mutex)(&tgt_prog->aux->ext_mutex);
|
||||||
|
tgt_prog->aux->is_extended = false;
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
hlist_del_init(&link->tramp_hlist);
|
hlist_del_init(&link->tramp_hlist);
|
||||||
@@ -604,12 +633,14 @@ static int __bpf_trampoline_unlink_prog(struct bpf_tramp_link *link, struct bpf_
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* bpf_trampoline_unlink_prog() should never fail. */
|
/* bpf_trampoline_unlink_prog() should never fail. */
|
||||||
int bpf_trampoline_unlink_prog(struct bpf_tramp_link *link, struct bpf_trampoline *tr)
|
int bpf_trampoline_unlink_prog(struct bpf_tramp_link *link,
|
||||||
|
struct bpf_trampoline *tr,
|
||||||
|
struct bpf_prog *tgt_prog)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
mutex_lock(&tr->mutex);
|
mutex_lock(&tr->mutex);
|
||||||
err = __bpf_trampoline_unlink_prog(link, tr);
|
err = __bpf_trampoline_unlink_prog(link, tr, tgt_prog);
|
||||||
mutex_unlock(&tr->mutex);
|
mutex_unlock(&tr->mutex);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@@ -624,7 +655,7 @@ static void bpf_shim_tramp_link_release(struct bpf_link *link)
|
|||||||
if (!shim_link->trampoline)
|
if (!shim_link->trampoline)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
WARN_ON_ONCE(bpf_trampoline_unlink_prog(&shim_link->link, shim_link->trampoline));
|
WARN_ON_ONCE(bpf_trampoline_unlink_prog(&shim_link->link, shim_link->trampoline, NULL));
|
||||||
bpf_trampoline_put(shim_link->trampoline);
|
bpf_trampoline_put(shim_link->trampoline);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -738,7 +769,7 @@ int bpf_trampoline_link_cgroup_shim(struct bpf_prog *prog,
|
|||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = __bpf_trampoline_link_prog(&shim_link->link, tr);
|
err = __bpf_trampoline_link_prog(&shim_link->link, tr, NULL);
|
||||||
if (err)
|
if (err)
|
||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user