Commit db775a8e authored by Leon Hwang's avatar Leon Hwang Committed by Tengda Wu
Browse files

bpf: Prevent tailcall infinite loop caused by freplace

mainline inclusion
from mainline-v6.13-rc1
commit d6083f040d5d8f8d748462c77e90547097df936e
category: bugfix
bugzilla: https://gitee.com/src-openeuler/kernel/issues/IBIADD
CVE: CVE-2024-47794

Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=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: default avatarEduard Zingerman <eddyz87@gmail.com>
Signed-off-by: default avatarLeon Hwang <leon.hwang@linux.dev>
Link: https://lore.kernel.org/r/20241015150207.70264-2-leon.hwang@linux.dev


Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
Conflicts:
	include/linux/bpf.h
	kernel/bpf/core.c
	kernel/bpf/syscall.c
[The conflicts are due to we did not merge commit 3e1c6f35409f, 4f9087f1665
and commit 3e1c6f35409]
Signed-off-by: default avatarTengda Wu <wutengda2@huawei.com>
parent d8b474e6
Loading
Loading
Loading
Loading
+13 −4
Original line number Diff line number Diff line
@@ -1292,8 +1292,12 @@ int bpf_dynptr_check_size(u32 size);
u32 __bpf_dynptr_size(const struct bpf_dynptr_kern *ptr);

#ifdef CONFIG_BPF_JIT
int bpf_trampoline_link_prog(struct bpf_tramp_link *link, struct bpf_trampoline *tr);
int bpf_trampoline_unlink_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 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_attach_target_info *tgt_info);
void bpf_trampoline_put(struct bpf_trampoline *tr);
@@ -1374,12 +1378,14 @@ void bpf_jit_uncharge_modmem(u32 size);
bool bpf_prog_has_trampoline(const struct bpf_prog *prog);
#else
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;
}
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;
}
@@ -1477,6 +1483,9 @@ struct bpf_prog_aux {
	bool sleepable;
	bool tail_call_reachable;
	bool xdp_has_frags;
	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 */
	/* BTF_KIND_FUNC_PROTO for valid attach_btf_id */
	const struct btf_type *attach_func_proto;
	/* function name for valid attach_btf_id */
+24 −2
Original line number Diff line number Diff line
@@ -909,22 +909,44 @@ 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 (!bpf_prog_map_compatible(map, prog)) {
	if (prog->type == BPF_PROG_TYPE_EXT ||
	    !bpf_prog_map_compatible(map, prog)) {
		bpf_prog_put(prog);
		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;
}

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_put(ptr);
	bpf_prog_put(prog);
}

static u32 prog_fd_array_sys_lookup_elem(void *ptr)
+1 −0
Original line number Diff line number Diff line
@@ -122,6 +122,7 @@ struct bpf_prog *bpf_prog_alloc_no_stats(unsigned int size, gfp_t gfp_extra_flag

	INIT_LIST_HEAD_RCU(&fp->aux->ksym.lnode);
	mutex_init(&fp->aux->used_maps_mutex);
	mutex_init(&fp->aux->ext_mutex);
	mutex_init(&fp->aux->dst_mutex);

	return fp;
+4 −3
Original line number Diff line number Diff line
@@ -3107,7 +3107,8 @@ static void bpf_tracing_link_release(struct bpf_link *link)
#endif

	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);

@@ -3325,7 +3326,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,
	if (err)
		goto out_unlock;

	err = bpf_trampoline_link_prog(&link->link, tr);
	err = bpf_trampoline_link_prog(&link->link, tr, tgt_prog);
	if (err) {
		bpf_link_cleanup(&link_primer);
		link = NULL;
+39 −8
Original line number Diff line number Diff line
@@ -513,7 +513,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;
	struct bpf_tramp_link *link_exiting;
@@ -534,6 +554,9 @@ static int __bpf_trampoline_link_prog(struct bpf_tramp_link *link, struct bpf_tr
		/* Cannot attach extension if fentry/fexit are in use. */
		if (cnt)
			return -EBUSY;
		err = bpf_freplace_check_tgt_prog(tgt_prog);
		if (err)
			return err;
		tr->extension_prog = link->link.prog;
		return bpf_arch_text_poke(tr->func.addr, BPF_MOD_JUMP, NULL,
					  link->link.prog->bpf_func);
@@ -560,17 +583,21 @@ static int __bpf_trampoline_link_prog(struct bpf_tramp_link *link, struct bpf_tr
	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;

	mutex_lock(&tr->mutex);
	err = __bpf_trampoline_link_prog(link, tr);
	err = __bpf_trampoline_link_prog(link, tr, tgt_prog);
	mutex_unlock(&tr->mutex);
	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;
	int err;
@@ -581,6 +608,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,
					 tr->extension_prog->bpf_func, NULL);
		tr->extension_prog = NULL;
		guard(mutex)(&tgt_prog->aux->ext_mutex);
		tgt_prog->aux->is_extended = false;
		return err;
	}
	hlist_del_init(&link->tramp_hlist);
@@ -589,12 +618,14 @@ static int __bpf_trampoline_unlink_prog(struct bpf_tramp_link *link, struct bpf_
}

/* 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;

	mutex_lock(&tr->mutex);
	err = __bpf_trampoline_unlink_prog(link, tr);
	err = __bpf_trampoline_unlink_prog(link, tr, tgt_prog);
	mutex_unlock(&tr->mutex);
	return err;
}
@@ -609,7 +640,7 @@ static void bpf_shim_tramp_link_release(struct bpf_link *link)
	if (!shim_link->trampoline)
		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);
}

@@ -723,7 +754,7 @@ int bpf_trampoline_link_cgroup_shim(struct bpf_prog *prog,
		goto err;
	}

	err = __bpf_trampoline_link_prog(&shim_link->link, tr);
	err = __bpf_trampoline_link_prog(&shim_link->link, tr, NULL);
	if (err)
		goto err;