1138 lines · 28.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* Copyright (c) 2019 Facebook */#include <linux/hash.h>#include <linux/bpf.h>#include <linux/filter.h>#include <linux/ftrace.h>#include <linux/rbtree_latch.h>#include <linux/perf_event.h>#include <linux/btf.h>#include <linux/rcupdate_trace.h>#include <linux/rcupdate_wait.h>#include <linux/static_call.h>#include <linux/bpf_verifier.h>#include <linux/bpf_lsm.h>#include <linux/delay.h> /* dummy _ops. The verifier will operate on target program's ops. */const struct bpf_verifier_ops bpf_extension_verifier_ops = {};const struct bpf_prog_ops bpf_extension_prog_ops = {}; /* btf_vmlinux has ~22k attachable functions. 1k htab is enough. */#define TRAMPOLINE_HASH_BITS 10#define TRAMPOLINE_TABLE_SIZE (1 << TRAMPOLINE_HASH_BITS) static struct hlist_head trampoline_table[TRAMPOLINE_TABLE_SIZE]; /* serializes access to trampoline_table */static DEFINE_MUTEX(trampoline_mutex); #ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLSstatic int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mutex); static int bpf_tramp_ftrace_ops_func(struct ftrace_ops *ops, enum ftrace_ops_cmd cmd){	struct bpf_trampoline *tr = ops->private;	int ret = 0; 	if (cmd == FTRACE_OPS_CMD_ENABLE_SHARE_IPMODIFY_SELF) {		/* This is called inside register_ftrace_direct_multi(), so		 * tr->mutex is already locked.		 */		lockdep_assert_held_once(&tr->mutex); 		/* Instead of updating the trampoline here, we propagate		 * -EAGAIN to register_ftrace_direct(). Then we can		 * retry register_ftrace_direct() after updating the		 * trampoline.		 */		if ((tr->flags & BPF_TRAMP_F_CALL_ORIG) &&		    !(tr->flags & BPF_TRAMP_F_ORIG_STACK)) {			if (WARN_ON_ONCE(tr->flags & BPF_TRAMP_F_SHARE_IPMODIFY))				return -EBUSY; 			tr->flags |= BPF_TRAMP_F_SHARE_IPMODIFY;			return -EAGAIN;		} 		return 0;	} 	/* The normal locking order is	 *    tr->mutex => direct_mutex (ftrace.c) => ftrace_lock (ftrace.c)	 *	 * The following two commands are called from	 *	 *   prepare_direct_functions_for_ipmodify	 *   cleanup_direct_functions_after_ipmodify	 *	 * In both cases, direct_mutex is already locked. Use	 * mutex_trylock(&tr->mutex) to avoid deadlock in race condition	 * (something else is making changes to this same trampoline).	 */	if (!mutex_trylock(&tr->mutex)) {		/* sleep 1 ms to make sure whatever holding tr->mutex makes		 * some progress.		 */		msleep(1);		return -EAGAIN;	} 	switch (cmd) {	case FTRACE_OPS_CMD_ENABLE_SHARE_IPMODIFY_PEER:		tr->flags |= BPF_TRAMP_F_SHARE_IPMODIFY; 		if ((tr->flags & BPF_TRAMP_F_CALL_ORIG) &&		    !(tr->flags & BPF_TRAMP_F_ORIG_STACK))			ret = bpf_trampoline_update(tr, false /* lock_direct_mutex */);		break;	case FTRACE_OPS_CMD_DISABLE_SHARE_IPMODIFY_PEER:		tr->flags &= ~BPF_TRAMP_F_SHARE_IPMODIFY; 		if (tr->flags & BPF_TRAMP_F_ORIG_STACK)			ret = bpf_trampoline_update(tr, false /* lock_direct_mutex */);		break;	default:		ret = -EINVAL;		break;	} 	mutex_unlock(&tr->mutex);	return ret;}#endif bool bpf_prog_has_trampoline(const struct bpf_prog *prog){	enum bpf_attach_type eatype = prog->expected_attach_type;	enum bpf_prog_type ptype = prog->type; 	return (ptype == BPF_PROG_TYPE_TRACING &&		(eatype == BPF_TRACE_FENTRY || eatype == BPF_TRACE_FEXIT ||		 eatype == BPF_MODIFY_RETURN)) ||		(ptype == BPF_PROG_TYPE_LSM && eatype == BPF_LSM_MAC);} void bpf_image_ksym_init(void *data, unsigned int size, struct bpf_ksym *ksym){	ksym->start = (unsigned long) data;	ksym->end = ksym->start + size;} void bpf_image_ksym_add(struct bpf_ksym *ksym){	bpf_ksym_add(ksym);	perf_event_ksymbol(PERF_RECORD_KSYMBOL_TYPE_BPF, ksym->start,			   PAGE_SIZE, false, ksym->name);} void bpf_image_ksym_del(struct bpf_ksym *ksym){	bpf_ksym_del(ksym);	perf_event_ksymbol(PERF_RECORD_KSYMBOL_TYPE_BPF, ksym->start,			   PAGE_SIZE, true, ksym->name);} static struct bpf_trampoline *bpf_trampoline_lookup(u64 key){	struct bpf_trampoline *tr;	struct hlist_head *head;	int i; 	mutex_lock(&trampoline_mutex);	head = &trampoline_table[hash_64(key, TRAMPOLINE_HASH_BITS)];	hlist_for_each_entry(tr, head, hlist) {		if (tr->key == key) {			refcount_inc(&tr->refcnt);			goto out;		}	}	tr = kzalloc(sizeof(*tr), GFP_KERNEL);	if (!tr)		goto out;#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS	tr->fops = kzalloc(sizeof(struct ftrace_ops), GFP_KERNEL);	if (!tr->fops) {		kfree(tr);		tr = NULL;		goto out;	}	tr->fops->private = tr;	tr->fops->ops_func = bpf_tramp_ftrace_ops_func;#endif 	tr->key = key;	INIT_HLIST_NODE(&tr->hlist);	hlist_add_head(&tr->hlist, head);	refcount_set(&tr->refcnt, 1);	mutex_init(&tr->mutex);	for (i = 0; i < BPF_TRAMP_MAX; i++)		INIT_HLIST_HEAD(&tr->progs_hlist[i]);out:	mutex_unlock(&trampoline_mutex);	return tr;} static int unregister_fentry(struct bpf_trampoline *tr, void *old_addr){	void *ip = tr->func.addr;	int ret; 	if (tr->func.ftrace_managed)		ret = unregister_ftrace_direct(tr->fops, (long)old_addr, false);	else		ret = bpf_arch_text_poke(ip, BPF_MOD_CALL, old_addr, NULL); 	return ret;} static int modify_fentry(struct bpf_trampoline *tr, void *old_addr, void *new_addr,			 bool lock_direct_mutex){	void *ip = tr->func.addr;	int ret; 	if (tr->func.ftrace_managed) {		if (lock_direct_mutex)			ret = modify_ftrace_direct(tr->fops, (long)new_addr);		else			ret = modify_ftrace_direct_nolock(tr->fops, (long)new_addr);	} else {		ret = bpf_arch_text_poke(ip, BPF_MOD_CALL, old_addr, new_addr);	}	return ret;} /* first time registering */static int register_fentry(struct bpf_trampoline *tr, void *new_addr){	void *ip = tr->func.addr;	unsigned long faddr;	int ret; 	faddr = ftrace_location((unsigned long)ip);	if (faddr) {		if (!tr->fops)			return -ENOTSUPP;		tr->func.ftrace_managed = true;	} 	if (tr->func.ftrace_managed) {		ftrace_set_filter_ip(tr->fops, (unsigned long)ip, 0, 1);		ret = register_ftrace_direct(tr->fops, (long)new_addr);	} else {		ret = bpf_arch_text_poke(ip, BPF_MOD_CALL, NULL, new_addr);	} 	return ret;} static struct bpf_tramp_links *bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_arg){	struct bpf_tramp_link *link;	struct bpf_tramp_links *tlinks;	struct bpf_tramp_link **links;	int kind; 	*total = 0;	tlinks = kcalloc(BPF_TRAMP_MAX, sizeof(*tlinks), GFP_KERNEL);	if (!tlinks)		return ERR_PTR(-ENOMEM); 	for (kind = 0; kind < BPF_TRAMP_MAX; kind++) {		tlinks[kind].nr_links = tr->progs_cnt[kind];		*total += tr->progs_cnt[kind];		links = tlinks[kind].links; 		hlist_for_each_entry(link, &tr->progs_hlist[kind], tramp_hlist) {			*ip_arg |= link->link.prog->call_get_func_ip;			*links++ = link;		}	}	return tlinks;} static void bpf_tramp_image_free(struct bpf_tramp_image *im){	bpf_image_ksym_del(&im->ksym);	arch_free_bpf_trampoline(im->image, im->size);	bpf_jit_uncharge_modmem(im->size);	percpu_ref_exit(&im->pcref);	kfree_rcu(im, rcu);} static void __bpf_tramp_image_put_deferred(struct work_struct *work){	struct bpf_tramp_image *im; 	im = container_of(work, struct bpf_tramp_image, work);	bpf_tramp_image_free(im);} /* callback, fexit step 3 or fentry step 2 */static void __bpf_tramp_image_put_rcu(struct rcu_head *rcu){	struct bpf_tramp_image *im; 	im = container_of(rcu, struct bpf_tramp_image, rcu);	INIT_WORK(&im->work, __bpf_tramp_image_put_deferred);	schedule_work(&im->work);} /* callback, fexit step 2. Called after percpu_ref_kill confirms. */static void __bpf_tramp_image_release(struct percpu_ref *pcref){	struct bpf_tramp_image *im; 	im = container_of(pcref, struct bpf_tramp_image, pcref);	call_rcu_tasks(&im->rcu, __bpf_tramp_image_put_rcu);} /* callback, fexit or fentry step 1 */static void __bpf_tramp_image_put_rcu_tasks(struct rcu_head *rcu){	struct bpf_tramp_image *im; 	im = container_of(rcu, struct bpf_tramp_image, rcu);	if (im->ip_after_call)		/* the case of fmod_ret/fexit trampoline and CONFIG_PREEMPTION=y */		percpu_ref_kill(&im->pcref);	else		/* the case of fentry trampoline */		call_rcu_tasks(&im->rcu, __bpf_tramp_image_put_rcu);} static void bpf_tramp_image_put(struct bpf_tramp_image *im){	/* The trampoline image that calls original function is using:	 * rcu_read_lock_trace to protect sleepable bpf progs	 * rcu_read_lock to protect normal bpf progs	 * percpu_ref to protect trampoline itself	 * rcu tasks to protect trampoline asm not covered by percpu_ref	 * (which are few asm insns before __bpf_tramp_enter and	 *  after __bpf_tramp_exit)	 *	 * The trampoline is unreachable before bpf_tramp_image_put().	 *	 * First, patch the trampoline to avoid calling into fexit progs.	 * The progs will be freed even if the original function is still	 * executing or sleeping.	 * In case of CONFIG_PREEMPT=y use call_rcu_tasks() to wait on	 * first few asm instructions to execute and call into	 * __bpf_tramp_enter->percpu_ref_get.	 * Then use percpu_ref_kill to wait for the trampoline and the original	 * function to finish.	 * Then use call_rcu_tasks() to make sure few asm insns in	 * the trampoline epilogue are done as well.	 *	 * In !PREEMPT case the task that got interrupted in the first asm	 * insns won't go through an RCU quiescent state which the	 * percpu_ref_kill will be waiting for. Hence the first	 * call_rcu_tasks() is not necessary.	 */	if (im->ip_after_call) {		int err = bpf_arch_text_poke(im->ip_after_call, BPF_MOD_JUMP,					     NULL, im->ip_epilogue);		WARN_ON(err);		if (IS_ENABLED(CONFIG_TASKS_RCU))			call_rcu_tasks(&im->rcu, __bpf_tramp_image_put_rcu_tasks);		else			percpu_ref_kill(&im->pcref);		return;	} 	/* The trampoline without fexit and fmod_ret progs doesn't call original	 * function and doesn't use percpu_ref.	 * Use call_rcu_tasks_trace() to wait for sleepable progs to finish.	 * Then use call_rcu_tasks() to wait for the rest of trampoline asm	 * and normal progs.	 */	call_rcu_tasks_trace(&im->rcu, __bpf_tramp_image_put_rcu_tasks);} static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size){	struct bpf_tramp_image *im;	struct bpf_ksym *ksym;	void *image;	int err = -ENOMEM; 	im = kzalloc(sizeof(*im), GFP_KERNEL);	if (!im)		goto out; 	err = bpf_jit_charge_modmem(size);	if (err)		goto out_free_im;	im->size = size; 	err = -ENOMEM;	im->image = image = arch_alloc_bpf_trampoline(size);	if (!image)		goto out_uncharge; 	err = percpu_ref_init(&im->pcref, __bpf_tramp_image_release, 0, GFP_KERNEL);	if (err)		goto out_free_image; 	ksym = &im->ksym;	INIT_LIST_HEAD_RCU(&ksym->lnode);	snprintf(ksym->name, KSYM_NAME_LEN, "bpf_trampoline_%llu", key);	bpf_image_ksym_init(image, size, ksym);	bpf_image_ksym_add(ksym);	return im; out_free_image:	arch_free_bpf_trampoline(im->image, im->size);out_uncharge:	bpf_jit_uncharge_modmem(size);out_free_im:	kfree(im);out:	return ERR_PTR(err);} static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mutex){	struct bpf_tramp_image *im;	struct bpf_tramp_links *tlinks;	u32 orig_flags = tr->flags;	bool ip_arg = false;	int err, total, size; 	tlinks = bpf_trampoline_get_progs(tr, &total, &ip_arg);	if (IS_ERR(tlinks))		return PTR_ERR(tlinks); 	if (total == 0) {		err = unregister_fentry(tr, tr->cur_image->image);		bpf_tramp_image_put(tr->cur_image);		tr->cur_image = NULL;		goto out;	} 	/* clear all bits except SHARE_IPMODIFY and TAIL_CALL_CTX */	tr->flags &= (BPF_TRAMP_F_SHARE_IPMODIFY | BPF_TRAMP_F_TAIL_CALL_CTX); 	if (tlinks[BPF_TRAMP_FEXIT].nr_links ||	    tlinks[BPF_TRAMP_MODIFY_RETURN].nr_links) {		/* NOTE: BPF_TRAMP_F_RESTORE_REGS and BPF_TRAMP_F_SKIP_FRAME		 * should not be set together.		 */		tr->flags |= BPF_TRAMP_F_CALL_ORIG | BPF_TRAMP_F_SKIP_FRAME;	} else {		tr->flags |= BPF_TRAMP_F_RESTORE_REGS;	} 	if (ip_arg)		tr->flags |= BPF_TRAMP_F_IP_ARG; #ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLSagain:	if ((tr->flags & BPF_TRAMP_F_SHARE_IPMODIFY) &&	    (tr->flags & BPF_TRAMP_F_CALL_ORIG))		tr->flags |= BPF_TRAMP_F_ORIG_STACK;#endif 	size = arch_bpf_trampoline_size(&tr->func.model, tr->flags,					tlinks, tr->func.addr);	if (size < 0) {		err = size;		goto out;	} 	if (size > PAGE_SIZE) {		err = -E2BIG;		goto out;	} 	im = bpf_tramp_image_alloc(tr->key, size);	if (IS_ERR(im)) {		err = PTR_ERR(im);		goto out;	} 	err = arch_prepare_bpf_trampoline(im, im->image, im->image + size,					  &tr->func.model, tr->flags, tlinks,					  tr->func.addr);	if (err < 0)		goto out_free; 	err = arch_protect_bpf_trampoline(im->image, im->size);	if (err)		goto out_free; 	WARN_ON(tr->cur_image && total == 0);	if (tr->cur_image)		/* progs already running at this address */		err = modify_fentry(tr, tr->cur_image->image, im->image, lock_direct_mutex);	else		/* first time registering */		err = register_fentry(tr, im->image); #ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS	if (err == -EAGAIN) {		/* -EAGAIN from bpf_tramp_ftrace_ops_func. Now		 * BPF_TRAMP_F_SHARE_IPMODIFY is set, we can generate the		 * trampoline again, and retry register.		 */		/* reset fops->func and fops->trampoline for re-register */		tr->fops->func = NULL;		tr->fops->trampoline = 0; 		/* free im memory and reallocate later */		bpf_tramp_image_free(im);		goto again;	}#endif	if (err)		goto out_free; 	if (tr->cur_image)		bpf_tramp_image_put(tr->cur_image);	tr->cur_image = im;out:	/* If any error happens, restore previous flags */	if (err)		tr->flags = orig_flags;

Lines 1–500 of 1138.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t