Files
Florent Revest (Anthropic) 17637e1a58 bpf: Skip detached progs in trampoline images that are still in use
bpf_tramp_image_put() makes sure a trampoline image is not freed while
a task may still be running in it, but nothing similar is done for the
progs called by that image. Detach drops the last prog reference right
away and the prog is freed after grace periods, on the basis that a
task still in the traced function skips the fexit progs once the nop at
ip_after_call is patched to a jump.

That leaves out a task sleeping in a sleepable prog that runs before
the detached one, which no grace period waits for:

  CPU 0                               CPU 1
  in image I, sleeping in prog S
                                      detach P from I's trampoline
                                       -> new image, bpf_tramp_image_put(I)
                                      bpf_prog_put(P), last ref
                                      grace periods, P freed
  back from S
  __bpf_prog_enter(P)
  call P->bpf_func

If S and P are fexit progs the task is already past the patched jump,
and fentry only images don't have one. On x86 this is an int3 in
poisoned bpf_prog_pack memory:

  Oops: int3: 0000 [#1] SMP NOPTI
  CPU: 18 UID: 0 PID: 94573 Comm: x169 Not tainted 6.18.44 #1 PREEMPT(lazy)
  RIP: 0010:0xffffffffc0601d8d
  Call Trace:
   <TASK>
   ? bpf_trampoline_6442515411+0x1a4/0x21b
   bpf_lsm_bprm_committed_creds+0x5/0x10
   security_bprm_committed_creds+0x5f/0x70
   begin_new_exec+0x2d6/0x410
   ...

We hit this in production when progs attached through trampolines got
detached while their hooks were busy, and it was independently found
with a fuzzer and KASAN.

Have the JITs emit a patchable nop in front of each prog call sequence
and record it in the image. When a prog is detached, patch its nop to a
jump over the call sequence. Progs that stay attached keep running for
the tasks that are in the image, and ip_after_call isn't needed anymore.

The task can be in any image that isn't freed yet, not only in the
current one. It sleeps in image I1 that calls S, P and Q, then P is
detached and the trampoline moves to image I2, then Q is detached and
its call is still in I1. So the trampoline keeps a list of its images
until they are freed, and detaching a prog patches its nop in all of
them. Images hold a reference on the trampoline for that long.

On riscv and loongarch a jump of any range takes several instructions,
and a task preempted in the middle of them could resume into half of the
new sequence. The nop is a single instruction there, patched to a near
branch through arch_bpf_trampoline_skip().

With the extra nops, BPF_MAX_TRAMP_LINKS progs no longer fit in a page
on x86 and arm64 (and already didn't on powerpc), so lower the limit
there like s390 does.

Fixes: e21aa34178 ("bpf: Fix fexit trampoline.")
Reported-by: Sechang Lim <[email protected]>
Suggested-by: Alexei Starovoitov <[email protected]>
Signed-off-by: Florent Revest (Anthropic) <[email protected]>
Signed-off-by: Alexei Starovoitov <[email protected]>
Link: https://patch.msgid.link/[email protected]
Closes: https://lore.kernel.org/bpf/[email protected]/
2026-09-29 05:54:16 +00:00

1873 lines
49 KiB
C

// 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_key_table[TRAMPOLINE_TABLE_SIZE];
static struct hlist_head trampoline_ip_table[TRAMPOLINE_TABLE_SIZE];
/* serializes access to trampoline tables */
static DEFINE_MUTEX(trampoline_mutex);
/*
* Keep 32 trampoline locks (5 bits) in the pool so trampoline_lock_all()
* stays below MAX_LOCK_DEPTH. Each pool slot has a distinct lockdep
* class because trampoline_lock_all() takes all pool mutexes at once;
* otherwise lockdep would report recursive locking on same-class mutexes.
*/
#define TRAMPOLINE_LOCKS_BITS 5
#define TRAMPOLINE_LOCKS_TABLE_SIZE (1 << TRAMPOLINE_LOCKS_BITS)
static struct {
struct mutex mutex;
struct lock_class_key key;
} trampoline_locks[TRAMPOLINE_LOCKS_TABLE_SIZE];
static struct mutex *select_trampoline_lock(struct bpf_trampoline *tr)
{
return &trampoline_locks[hash_ptr(tr, TRAMPOLINE_LOCKS_BITS)].mutex;
}
static void trampoline_lock(struct bpf_trampoline *tr)
{
mutex_lock(select_trampoline_lock(tr));
}
static void trampoline_unlock(struct bpf_trampoline *tr)
{
mutex_unlock(select_trampoline_lock(tr));
}
struct bpf_trampoline_ops {
int (*register_fentry)(struct bpf_trampoline *tr, struct bpf_tramp_image *im, void *data);
int (*unregister_fentry)(struct bpf_trampoline *tr, u32 orig_flags, void *data);
int (*modify_fentry)(struct bpf_trampoline *tr, u32 orig_flags, struct bpf_tramp_image *im,
bool lock_direct_mutex, void *data);
};
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mutex,
const struct bpf_trampoline_ops *ops, void *data);
static const struct bpf_trampoline_ops trampoline_ops;
#ifdef CONFIG_HAVE_SINGLE_FTRACE_DIRECT_OPS
static struct bpf_trampoline *direct_ops_ip_lookup(struct ftrace_ops *ops, unsigned long ip)
{
struct hlist_head *head_ip;
struct bpf_trampoline *tr;
mutex_lock(&trampoline_mutex);
head_ip = &trampoline_ip_table[hash_64(ip, TRAMPOLINE_HASH_BITS)];
hlist_for_each_entry(tr, head_ip, hlist_ip) {
if (tr->ip == ip)
goto out;
}
tr = NULL;
out:
mutex_unlock(&trampoline_mutex);
return tr;
}
#else
static struct bpf_trampoline *direct_ops_ip_lookup(struct ftrace_ops *ops, unsigned long ip)
{
return ops->private;
}
#endif /* CONFIG_HAVE_SINGLE_FTRACE_DIRECT_OPS */
static int bpf_tramp_ftrace_ops_func(struct ftrace_ops *ops, unsigned long ip,
enum ftrace_ops_cmd cmd)
{
struct bpf_trampoline *tr;
int ret = 0;
tr = direct_ops_ip_lookup(ops, ip);
if (!tr)
return -EINVAL;
if (cmd == FTRACE_OPS_CMD_ENABLE_SHARE_IPMODIFY_SELF) {
/* This is called inside register_ftrace_direct_multi(), so
* trampoline's mutex is already locked.
*/
lockdep_assert_held_once(select_trampoline_lock(tr));
/* 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
* select_trampoline_lock(tr) => 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(select_trampoline_lock(tr)) to avoid deadlock in race condition
* (something else holds the same pool lock).
*/
if (!mutex_trylock(select_trampoline_lock(tr))) {
/* sleep 1 ms to make sure whatever holding select_trampoline_lock(tr)
* 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 */,
&trampoline_ops, NULL);
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 */,
&trampoline_ops, NULL);
break;
default:
ret = -EINVAL;
break;
}
trampoline_unlock(tr);
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;
switch (ptype) {
case BPF_PROG_TYPE_TRACING:
if (eatype == BPF_TRACE_FENTRY || eatype == BPF_TRACE_FEXIT ||
eatype == BPF_MODIFY_RETURN || eatype == BPF_TRACE_FSESSION ||
eatype == BPF_TRACE_FENTRY_MULTI || eatype == BPF_TRACE_FEXIT_MULTI ||
eatype == BPF_TRACE_FSESSION_MULTI)
return true;
return false;
case BPF_PROG_TYPE_LSM:
return eatype == BPF_LSM_MAC;
default:
return false;
}
}
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);
}
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
#ifdef CONFIG_HAVE_SINGLE_FTRACE_DIRECT_OPS
/*
* We have only single direct_ops which contains all the direct call
* sites and is the only global ftrace_ops for all trampolines.
*
* We use 'update_ftrace_direct_*' api for attachment.
*/
struct ftrace_ops direct_ops = {
.ops_func = bpf_tramp_ftrace_ops_func,
};
static int direct_ops_alloc(struct bpf_trampoline *tr)
{
tr->fops = &direct_ops;
return 0;
}
static void direct_ops_free(struct bpf_trampoline *tr) { }
static struct ftrace_hash *hash_from_ip(struct bpf_trampoline *tr, void *ptr)
{
unsigned long ip, addr = (unsigned long) ptr;
struct ftrace_hash *hash;
ip = ftrace_location(tr->ip);
if (!ip)
return NULL;
hash = alloc_ftrace_hash(FTRACE_HASH_DEFAULT_BITS);
if (!hash)
return NULL;
if (bpf_trampoline_use_jmp(tr->flags))
addr = ftrace_jmp_set(addr);
if (!add_ftrace_hash_entry_direct(hash, ip, addr)) {
free_ftrace_hash(hash);
return NULL;
}
return hash;
}
static int direct_ops_add(struct bpf_trampoline *tr, void *addr)
{
struct ftrace_hash *hash = hash_from_ip(tr, addr);
int err;
if (!hash)
return -ENOMEM;
err = update_ftrace_direct_add(tr->fops, hash);
free_ftrace_hash(hash);
return err;
}
static int direct_ops_del(struct bpf_trampoline *tr, void *addr)
{
struct ftrace_hash *hash = hash_from_ip(tr, addr);
int err;
if (!hash)
return -ENOMEM;
err = update_ftrace_direct_del(tr->fops, hash);
free_ftrace_hash(hash);
return err;
}
static int direct_ops_mod(struct bpf_trampoline *tr, void *addr, bool lock_direct_mutex)
{
struct ftrace_hash *hash = hash_from_ip(tr, addr);
int err;
if (!hash)
return -ENOMEM;
err = update_ftrace_direct_mod(tr->fops, hash, lock_direct_mutex);
free_ftrace_hash(hash);
return err;
}
#else
/*
* We allocate ftrace_ops object for each trampoline and it contains
* call site specific for that trampoline.
*
* We use *_ftrace_direct api for attachment.
*/
static int direct_ops_alloc(struct bpf_trampoline *tr)
{
tr->fops = kzalloc_obj(struct ftrace_ops);
if (!tr->fops)
return -ENOMEM;
tr->fops->private = tr;
tr->fops->ops_func = bpf_tramp_ftrace_ops_func;
return 0;
}
static void direct_ops_free(struct bpf_trampoline *tr)
{
if (!tr->fops)
return;
ftrace_free_filter(tr->fops);
kfree(tr->fops);
}
static int direct_ops_add(struct bpf_trampoline *tr, void *ptr)
{
unsigned long addr = (unsigned long) ptr;
struct ftrace_ops *ops = tr->fops;
int ret;
if (bpf_trampoline_use_jmp(tr->flags))
addr = ftrace_jmp_set(addr);
ret = ftrace_set_filter_ip(ops, tr->ip, 0, 1);
if (ret)
return ret;
return register_ftrace_direct(ops, addr);
}
static int direct_ops_del(struct bpf_trampoline *tr, void *addr)
{
return unregister_ftrace_direct(tr->fops, (long)addr, false);
}
static int direct_ops_mod(struct bpf_trampoline *tr, void *ptr, bool lock_direct_mutex)
{
unsigned long addr = (unsigned long) ptr;
struct ftrace_ops *ops = tr->fops;
if (bpf_trampoline_use_jmp(tr->flags))
addr = ftrace_jmp_set(addr);
if (lock_direct_mutex)
return modify_ftrace_direct(ops, addr);
return modify_ftrace_direct_nolock(ops, addr);
}
#endif /* CONFIG_HAVE_SINGLE_FTRACE_DIRECT_OPS */
#else
static void direct_ops_free(struct bpf_trampoline *tr) { }
static int direct_ops_alloc(struct bpf_trampoline *tr)
{
return 0;
}
static int direct_ops_add(struct bpf_trampoline *tr, void *addr)
{
return -ENODEV;
}
static int direct_ops_del(struct bpf_trampoline *tr, void *addr)
{
return -ENODEV;
}
static int direct_ops_mod(struct bpf_trampoline *tr, void *ptr, bool lock_direct_mutex)
{
return -ENODEV;
}
#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */
static struct bpf_trampoline *bpf_trampoline_lookup(u64 key, unsigned long ip)
{
struct bpf_trampoline *tr;
struct hlist_head *head;
int i;
mutex_lock(&trampoline_mutex);
head = &trampoline_key_table[hash_64(key, TRAMPOLINE_HASH_BITS)];
hlist_for_each_entry(tr, head, hlist_key) {
if (tr->key == key) {
refcount_inc(&tr->refcnt);
goto out;
}
}
tr = kzalloc_obj(*tr);
if (!tr)
goto out;
if (direct_ops_alloc(tr)) {
kfree(tr);
tr = NULL;
goto out;
}
tr->key = key;
tr->ip = ftrace_location(ip);
INIT_HLIST_NODE(&tr->hlist_key);
INIT_HLIST_NODE(&tr->hlist_ip);
hlist_add_head(&tr->hlist_key, head);
head = &trampoline_ip_table[hash_64(tr->ip, TRAMPOLINE_HASH_BITS)];
hlist_add_head(&tr->hlist_ip, head);
refcount_set(&tr->refcnt, 1);
INIT_LIST_HEAD(&tr->images);
for (i = 0; i < BPF_TRAMP_MAX; i++)
INIT_HLIST_HEAD(&tr->progs_hlist[i]);
out:
mutex_unlock(&trampoline_mutex);
return tr;
}
static int bpf_trampoline_update_fentry(struct bpf_trampoline *tr, u32 orig_flags,
void *old_addr, void *new_addr)
{
enum bpf_text_poke_type new_t = BPF_MOD_CALL, old_t = BPF_MOD_CALL;
void *ip = tr->func.addr;
if (!new_addr)
new_t = BPF_MOD_NOP;
else if (bpf_trampoline_use_jmp(tr->flags))
new_t = BPF_MOD_JUMP;
if (!old_addr)
old_t = BPF_MOD_NOP;
else if (bpf_trampoline_use_jmp(orig_flags))
old_t = BPF_MOD_JUMP;
return bpf_arch_text_poke(ip, old_t, new_t, old_addr, new_addr);
}
static void bpf_tramp_image_put(struct bpf_tramp_image *im);
static int unregister_fentry(struct bpf_trampoline *tr, u32 orig_flags, void *data __maybe_unused)
{
void *old_addr = tr->cur_image->image;
int ret;
if (tr->func.ftrace_managed)
ret = direct_ops_del(tr, old_addr);
else
ret = bpf_trampoline_update_fentry(tr, orig_flags, old_addr, NULL);
if (ret)
return ret;
bpf_tramp_image_put(tr->cur_image);
tr->cur_image = NULL;
return 0;
}
static int modify_fentry(struct bpf_trampoline *tr, u32 orig_flags, struct bpf_tramp_image *im,
bool lock_direct_mutex, void *data __maybe_unused)
{
void *old_addr = tr->cur_image->image;
void *new_addr = im->image;
int ret;
if (tr->func.ftrace_managed) {
ret = direct_ops_mod(tr, new_addr, lock_direct_mutex);
} else {
ret = bpf_trampoline_update_fentry(tr, orig_flags, old_addr,
new_addr);
}
if (ret)
return ret;
bpf_tramp_image_put(tr->cur_image);
tr->cur_image = im;
return 0;
}
/* first time registering */
static int register_fentry(struct bpf_trampoline *tr, struct bpf_tramp_image *im,
void *data __maybe_unused)
{
void *new_addr = im->image;
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) {
ret = direct_ops_add(tr, new_addr);
} else {
ret = bpf_trampoline_update_fentry(tr, 0, NULL, new_addr);
}
if (ret)
return ret;
tr->cur_image = im;
return 0;
}
static const struct bpf_trampoline_ops trampoline_ops = {
.register_fentry = register_fentry,
.unregister_fentry = unregister_fentry,
.modify_fentry = modify_fentry,
};
static struct bpf_tramp_nodes *
bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_arg)
{
struct bpf_tramp_node *node, **nodes;
struct bpf_tramp_nodes *tnodes;
int kind;
*total = 0;
tnodes = kzalloc_objs(*tnodes, BPF_TRAMP_MAX);
if (!tnodes)
return ERR_PTR(-ENOMEM);
for (kind = 0; kind < BPF_TRAMP_MAX; kind++) {
tnodes[kind].nr_nodes = tr->progs_cnt[kind];
*total += tr->progs_cnt[kind];
nodes = tnodes[kind].nodes;
hlist_for_each_entry(node, &tr->progs_hlist[kind], tramp_hlist) {
*ip_arg |= node->link->prog->call_get_func_ip;
*nodes++ = node;
}
}
return tnodes;
}
/*
* The arena base against which save_args() converts the arguments marked
* with BTF_FMODEL_ARENA_ARG. Only the struct_ops indirect trampoline
* converts: it dispatches to a single prog whose arena is known at
* generation time. Return 0 when there is nothing to convert.
*/
u64 bpf_tramp_arena_base(const struct btf_func_model *m,
struct bpf_tramp_nodes *tnodes, u32 flags)
{
const struct bpf_prog *prog;
int i;
if (!(flags & BPF_TRAMP_F_INDIRECT) ||
tnodes[BPF_TRAMP_FENTRY].nr_nodes != 1)
return 0;
for (i = 0; i < m->nr_args; i++)
if (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG)
break;
if (i == m->nr_args)
return 0;
/* Verification rejects an arena argument without an arena. */
prog = tnodes[BPF_TRAMP_FENTRY].nodes[0]->link->prog;
if (WARN_ON_ONCE(!prog->aux->arena))
return 0;
return bpf_arena_get_kern_vm_start(prog->aux->arena);
}
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(im->skips);
kfree_rcu(im, rcu);
}
static void __bpf_tramp_image_put_deferred(struct work_struct *work)
{
struct bpf_tramp_image *im;
struct bpf_trampoline *tr;
im = container_of(work, struct bpf_tramp_image, work);
tr = im->tr;
trampoline_lock(tr);
list_del(&im->list);
trampoline_unlock(tr);
bpf_tramp_image_free(im);
bpf_trampoline_put(tr);
}
/* 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->call_orig)
/* 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().
*
* Progs are patched out of the image when they are detached, see
* bpf_trampoline_skip_prog(), so they can be freed even if a task is
* still in the image.
* 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->call_orig) {
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, int nr_progs)
{
struct bpf_tramp_image *im;
struct bpf_ksym *ksym;
void *image;
int err = -ENOMEM;
im = kzalloc_obj(*im);
if (!im)
goto out;
im->skips = kzalloc_objs(*im->skips, nr_progs);
if (!im->skips)
goto out_free_im;
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->skips);
kfree(im);
out:
return ERR_PTR(err);
}
void bpf_trampoline_set_flags(struct bpf_trampoline *tr, u32 flags)
{
trampoline_lock(tr);
tr->flags |= flags;
trampoline_unlock(tr);
}
static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mutex,
const struct bpf_trampoline_ops *ops, void *data)
{
struct bpf_tramp_image *im;
struct bpf_tramp_nodes *tnodes;
u32 orig_flags = tr->flags;
bool ip_arg = false;
int err, total, size;
tnodes = bpf_trampoline_get_progs(tr, &total, &ip_arg);
if (IS_ERR(tnodes))
return PTR_ERR(tnodes);
if (total == 0) {
err = ops->unregister_fentry(tr, orig_flags, data);
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 (tnodes[BPF_TRAMP_FEXIT].nr_nodes ||
tnodes[BPF_TRAMP_MODIFY_RETURN].nr_nodes) {
/* 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_CALLS
again:
if (tr->flags & BPF_TRAMP_F_CALL_ORIG) {
if (tr->flags & BPF_TRAMP_F_SHARE_IPMODIFY) {
/* The BPF_TRAMP_F_SKIP_FRAME can be cleared in the
* first try, reset it in the second try.
*/
tr->flags |= BPF_TRAMP_F_ORIG_STACK | BPF_TRAMP_F_SKIP_FRAME;
} else if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_JMP)) {
/* Use "jmp" instead of "call" for the trampoline
* in the origin call case, and we don't need to
* skip the frame.
*/
tr->flags &= ~BPF_TRAMP_F_SKIP_FRAME;
}
}
#endif
size = arch_bpf_trampoline_size(&tr->func.model, tr->flags,
tnodes, 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, total);
if (IS_ERR(im)) {
err = PTR_ERR(im);
goto out;
}
im->call_orig = tr->flags & BPF_TRAMP_F_CALL_ORIG;
err = arch_prepare_bpf_trampoline(im, im->image, im->image + size,
&tr->func.model, tr->flags, tnodes,
tr->func.addr);
if (err < 0)
goto out_free;
err = arch_protect_bpf_trampoline(im->image, im->size);
if (err)
goto out_free;
if (tr->cur_image)
/* progs already running at this address */
err = ops->modify_fentry(tr, orig_flags, im, lock_direct_mutex, data);
else
/* first time registering */
err = ops->register_fentry(tr, im, data);
#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.
*/
bpf_tramp_image_free(im);
goto again;
}
#endif
out_free:
if (err) {
bpf_tramp_image_free(im);
} else {
/* track the image until it is freed, for bpf_trampoline_skip_prog() */
refcount_inc(&tr->refcnt);
im->tr = tr;
list_add(&im->list, &tr->images);
}
out:
/* If any error happens, restore previous flags */
if (err)
tr->flags = orig_flags;
kfree(tnodes);
return err;
}
static enum bpf_tramp_prog_type bpf_attach_type_to_tramp(struct bpf_prog *prog)
{
switch (prog->expected_attach_type) {
case BPF_TRACE_FENTRY:
case BPF_TRACE_FENTRY_MULTI:
return BPF_TRAMP_FENTRY;
case BPF_MODIFY_RETURN:
return BPF_TRAMP_MODIFY_RETURN;
case BPF_TRACE_FEXIT:
case BPF_TRACE_FEXIT_MULTI:
return BPF_TRAMP_FEXIT;
case BPF_TRACE_FSESSION:
case BPF_TRACE_FSESSION_MULTI:
return BPF_TRAMP_FSESSION;
case BPF_LSM_MAC:
if (!prog->aux->attach_func_proto->type)
/* The function returns void, we cannot modify its
* return value.
*/
return BPF_TRAMP_FEXIT;
else
return BPF_TRAMP_MODIFY_RETURN;
default:
return BPF_TRAMP_REPLACE;
}
}
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 struct bpf_tramp_node *fsession_exit(struct bpf_tramp_node *node)
{
if (node->link->type == BPF_LINK_TYPE_TRACING) {
struct bpf_tracing_link *link;
link = container_of(node->link, struct bpf_tracing_link, link.link);
return &link->fexit;
} else if (node->link->type == BPF_LINK_TYPE_TRACING_MULTI) {
struct bpf_tracing_multi_link *link;
struct bpf_tracing_multi_node *mnode;
link = container_of(node->link, struct bpf_tracing_multi_link, link);
mnode = container_of(node, struct bpf_tracing_multi_node, node);
return &link->fexits[mnode - link->nodes];
}
return NULL;
}
static int bpf_trampoline_add_prog(struct bpf_trampoline *tr,
struct bpf_tramp_node *node,
int cnt)
{
enum bpf_tramp_prog_type kind;
struct bpf_tramp_node *node_existing, *fexit;
struct hlist_head *prog_list;
kind = bpf_attach_type_to_tramp(node->link->prog);
if (kind == BPF_TRAMP_FSESSION) {
prog_list = &tr->progs_hlist[BPF_TRAMP_FENTRY];
cnt++;
} else {
prog_list = &tr->progs_hlist[kind];
}
if (cnt >= BPF_MAX_TRAMP_LINKS)
return -E2BIG;
if (!hlist_unhashed(&node->tramp_hlist))
/* prog already linked */
return -EBUSY;
hlist_for_each_entry(node_existing, prog_list, tramp_hlist) {
if (node_existing->link->prog != node->link->prog)
continue;
/* prog already linked */
return -EBUSY;
}
hlist_add_head(&node->tramp_hlist, prog_list);
node->link->prog->aux->tramp_linked = true;
if (kind == BPF_TRAMP_FSESSION) {
tr->progs_cnt[BPF_TRAMP_FENTRY]++;
fexit = fsession_exit(node);
if (WARN_ON_ONCE(!fexit))
return -EINVAL;
hlist_add_head(&fexit->tramp_hlist, &tr->progs_hlist[BPF_TRAMP_FEXIT]);
tr->progs_cnt[BPF_TRAMP_FEXIT]++;
} else {
tr->progs_cnt[kind]++;
}
return 0;
}
/*
* Patch the nop in front of a prog call to a jump over it. A task can be
* preempted anywhere in the image, so archs that need several instructions for
* a jump of any range patch a single near branch here instead.
*/
int __weak arch_bpf_trampoline_skip(void *nop, void *target)
{
return bpf_arch_text_poke(nop, BPF_MOD_NOP, BPF_MOD_JUMP, NULL, target);
}
/*
* prog was detached and can be freed, but tasks may still be running in images
* that call it, sleeping in an earlier prog for example. They can be in any
* image that is not freed yet, not only in cur_image, so patch all of them to
* jump over prog.
*/
static void bpf_trampoline_skip_prog(struct bpf_trampoline *tr, struct bpf_prog *prog)
{
struct bpf_tramp_image *im;
int i, err;
list_for_each_entry(im, &tr->images, list) {
for (i = 0; i < im->nr_skips; i++) {
struct bpf_tramp_skip *skip = &im->skips[i];
if (skip->prog != prog)
continue;
err = arch_bpf_trampoline_skip(skip->nop, skip->target);
WARN_ON_ONCE(err);
/* not a nop anymore, and prog's address can be reused */
skip->prog = NULL;
}
}
}
static void bpf_trampoline_remove_prog(struct bpf_trampoline *tr,
struct bpf_tramp_node *node)
{
enum bpf_tramp_prog_type kind;
struct bpf_tramp_node *fexit;
kind = bpf_attach_type_to_tramp(node->link->prog);
if (kind == BPF_TRAMP_FSESSION) {
fexit = fsession_exit(node);
if (WARN_ON_ONCE(!fexit))
return;
hlist_del_init(&fexit->tramp_hlist);
tr->progs_cnt[BPF_TRAMP_FEXIT]--;
kind = BPF_TRAMP_FENTRY;
}
hlist_del_init(&node->tramp_hlist);
tr->progs_cnt[kind]--;
bpf_trampoline_skip_prog(tr, node->link->prog);
}
static int __bpf_trampoline_link_prog(struct bpf_tramp_node *node,
struct bpf_trampoline *tr,
struct bpf_prog *tgt_prog,
const struct bpf_trampoline_ops *ops,
void *data)
{
enum bpf_tramp_prog_type kind;
int err = 0;
int cnt = 0, i;
kind = bpf_attach_type_to_tramp(node->link->prog);
/*
* Arena ctx args are converted only by struct_ops indirect
* trampolines. They must never be attached to a generic trampoline.
*/
if (WARN_ON_ONCE(bpf_prog_has_arena_ctx_arg(node->link->prog)))
return -ENOTSUPP;
if (tr->extension_prog)
/* cannot attach fentry/fexit if extension prog is attached.
* cannot overwrite extension prog either.
*/
return -EBUSY;
for (i = 0; i < BPF_TRAMP_MAX; i++)
cnt += tr->progs_cnt[i];
if (kind == BPF_TRAMP_REPLACE) {
/* 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 = node->link->prog;
return bpf_arch_text_poke(tr->func.addr, BPF_MOD_NOP,
BPF_MOD_JUMP, NULL,
node->link->prog->bpf_func);
}
err = bpf_trampoline_add_prog(tr, node, cnt);
if (err)
return err;
err = bpf_trampoline_update(tr, true /* lock_direct_mutex */, ops, data);
if (err)
bpf_trampoline_remove_prog(tr, node);
return err;
}
int bpf_trampoline_link_prog(struct bpf_tramp_node *node,
struct bpf_trampoline *tr,
struct bpf_prog *tgt_prog)
{
int err;
trampoline_lock(tr);
err = __bpf_trampoline_link_prog(node, tr, tgt_prog, &trampoline_ops, NULL);
trampoline_unlock(tr);
return err;
}
static int __bpf_trampoline_unlink_prog(struct bpf_tramp_node *node,
struct bpf_trampoline *tr,
struct bpf_prog *tgt_prog,
const struct bpf_trampoline_ops *ops,
void *data)
{
enum bpf_tramp_prog_type kind;
int err;
kind = bpf_attach_type_to_tramp(node->link->prog);
if (kind == BPF_TRAMP_REPLACE) {
WARN_ON_ONCE(!tr->extension_prog);
err = bpf_arch_text_poke(tr->func.addr, BPF_MOD_JUMP,
BPF_MOD_NOP,
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;
}
bpf_trampoline_remove_prog(tr, node);
return bpf_trampoline_update(tr, true /* lock_direct_mutex */, ops, data);
}
/* bpf_trampoline_unlink_prog() should never fail. */
int bpf_trampoline_unlink_prog(struct bpf_tramp_node *node,
struct bpf_trampoline *tr,
struct bpf_prog *tgt_prog)
{
int err;
trampoline_lock(tr);
err = __bpf_trampoline_unlink_prog(node, tr, tgt_prog, &trampoline_ops, NULL);
trampoline_unlock(tr);
return err;
}
#if defined(CONFIG_CGROUP_BPF) && defined(CONFIG_BPF_LSM)
static void bpf_shim_tramp_link_release(struct bpf_link *link)
{
struct bpf_shim_tramp_link *shim_link =
container_of(link, struct bpf_shim_tramp_link, link.link);
int err;
/* paired with 'shim_link->trampoline = tr' in bpf_trampoline_link_cgroup_shim */
if (!shim_link->trampoline)
return;
err = bpf_trampoline_unlink_prog(&shim_link->link.node, shim_link->trampoline, NULL);
WARN_ONCE(err, "bpf_trampoline_unlink_prog failed: %d\n", err);
bpf_trampoline_put(shim_link->trampoline);
}
static void bpf_shim_tramp_link_dealloc(struct bpf_link *link)
{
struct bpf_shim_tramp_link *shim_link =
container_of(link, struct bpf_shim_tramp_link, link.link);
kfree(shim_link);
}
static const struct bpf_link_ops bpf_shim_tramp_link_lops = {
.release = bpf_shim_tramp_link_release,
.dealloc = bpf_shim_tramp_link_dealloc,
};
static struct bpf_shim_tramp_link *cgroup_shim_alloc(const struct bpf_prog *prog,
bpf_func_t bpf_func,
int cgroup_atype,
enum bpf_attach_type attach_type)
{
struct bpf_shim_tramp_link *shim_link = NULL;
struct bpf_prog *p;
shim_link = kzalloc_obj(*shim_link, GFP_USER);
if (!shim_link)
return NULL;
p = bpf_prog_alloc(1, 0);
if (!p) {
kfree(shim_link);
return NULL;
}
p->jited = false;
p->bpf_func = bpf_func;
p->aux->cgroup_atype = cgroup_atype;
p->aux->attach_func_proto = prog->aux->attach_func_proto;
p->aux->attach_btf_id = prog->aux->attach_btf_id;
p->aux->attach_btf = prog->aux->attach_btf;
btf_get(p->aux->attach_btf);
p->type = BPF_PROG_TYPE_LSM;
p->expected_attach_type = BPF_LSM_MAC;
bpf_prog_inc(p);
bpf_tramp_link_init(&shim_link->link, BPF_LINK_TYPE_UNSPEC,
&bpf_shim_tramp_link_lops, p, attach_type, 0);
bpf_cgroup_atype_get(p->aux->attach_btf_id, cgroup_atype);
return shim_link;
}
static struct bpf_shim_tramp_link *cgroup_shim_find(struct bpf_trampoline *tr,
bpf_func_t bpf_func)
{
struct bpf_tramp_node *node;
int kind;
for (kind = 0; kind < BPF_TRAMP_MAX; kind++) {
hlist_for_each_entry(node, &tr->progs_hlist[kind], tramp_hlist) {
struct bpf_prog *p = node->link->prog;
if (p->bpf_func == bpf_func)
return container_of(node, struct bpf_shim_tramp_link, link.node);
}
}
return NULL;
}
int bpf_trampoline_link_cgroup_shim(struct bpf_prog *prog,
int cgroup_atype,
enum bpf_attach_type attach_type)
{
struct bpf_shim_tramp_link *shim_link = NULL;
struct bpf_attach_target_info tgt_info = {};
struct bpf_trampoline *tr;
bpf_func_t bpf_func;
u64 key;
int err;
err = bpf_check_attach_target(NULL, prog, NULL,
prog->aux->attach_btf_id,
&tgt_info);
if (err)
return err;
key = bpf_trampoline_compute_key(NULL, prog->aux->attach_btf,
prog->aux->attach_btf_id);
bpf_lsm_find_cgroup_shim(prog, &bpf_func);
tr = bpf_trampoline_get(key, &tgt_info);
if (!tr)
return -ENOMEM;
trampoline_lock(tr);
shim_link = cgroup_shim_find(tr, bpf_func);
if (shim_link && !IS_ERR(bpf_link_inc_not_zero(&shim_link->link.link))) {
/* Reusing existing shim attached by the other program. */
trampoline_unlock(tr);
bpf_trampoline_put(tr); /* bpf_trampoline_get above */
return 0;
}
/* Allocate and install new shim. */
shim_link = cgroup_shim_alloc(prog, bpf_func, cgroup_atype, attach_type);
if (!shim_link) {
err = -ENOMEM;
goto err;
}
err = __bpf_trampoline_link_prog(&shim_link->link.node, tr, NULL, &trampoline_ops, NULL);
if (err)
goto err;
shim_link->trampoline = tr;
/* note, we're still holding tr refcnt from above */
trampoline_unlock(tr);
return 0;
err:
trampoline_unlock(tr);
if (shim_link)
bpf_link_put(&shim_link->link.link);
/* have to release tr while _not_ holding pool mutex for trampoline */
bpf_trampoline_put(tr); /* bpf_trampoline_get above */
return err;
}
void bpf_trampoline_unlink_cgroup_shim(struct bpf_prog *prog)
{
struct bpf_shim_tramp_link *shim_link = NULL;
struct bpf_trampoline *tr;
bpf_func_t bpf_func;
u64 key;
key = bpf_trampoline_compute_key(NULL, prog->aux->attach_btf,
prog->aux->attach_btf_id);
bpf_lsm_find_cgroup_shim(prog, &bpf_func);
tr = bpf_trampoline_lookup(key, 0);
if (WARN_ON_ONCE(!tr))
return;
trampoline_lock(tr);
shim_link = cgroup_shim_find(tr, bpf_func);
trampoline_unlock(tr);
if (shim_link)
bpf_link_put(&shim_link->link.link);
bpf_trampoline_put(tr); /* bpf_trampoline_lookup above */
}
#endif
struct bpf_trampoline *bpf_trampoline_get(u64 key,
struct bpf_attach_target_info *tgt_info)
{
struct bpf_trampoline *tr;
tr = bpf_trampoline_lookup(key, tgt_info->tgt_addr);
if (!tr)
return NULL;
trampoline_lock(tr);
if (tr->func.addr)
goto out;
memcpy(&tr->func.model, &tgt_info->fmodel, sizeof(tgt_info->fmodel));
tr->func.addr = (void *)tgt_info->tgt_addr;
out:
trampoline_unlock(tr);
return tr;
}
void bpf_trampoline_put(struct bpf_trampoline *tr)
{
int i;
if (!tr)
return;
mutex_lock(&trampoline_mutex);
if (!refcount_dec_and_test(&tr->refcnt))
goto out;
for (i = 0; i < BPF_TRAMP_MAX; i++)
if (WARN_ON_ONCE(!hlist_empty(&tr->progs_hlist[i])))
goto out;
/*
* All progs are detached and the last image has been freed, images
* hold a reference on the trampoline until then.
*/
hlist_del(&tr->hlist_key);
hlist_del(&tr->hlist_ip);
direct_ops_free(tr);
kfree(tr);
out:
mutex_unlock(&trampoline_mutex);
}
#define NO_START_TIME 1
static __always_inline u64 notrace bpf_prog_start_time(void)
{
u64 start = NO_START_TIME;
if (static_branch_unlikely(&bpf_stats_enabled_key)) {
start = sched_clock();
if (unlikely(!start))
start = NO_START_TIME;
}
return start;
}
/* The logic is similar to bpf_prog_run(), but with an explicit
* rcu_read_lock() and migrate_disable() which are required
* for the trampoline. The macro is split into
* call __bpf_prog_enter
* call prog->bpf_func
* call __bpf_prog_exit
*
* __bpf_prog_enter returns:
* 0 - skip execution of the bpf prog
* 1 - execute bpf prog
* [2..MAX_U64] - execute bpf prog and record execution time.
* This is start time.
*/
static u64 notrace __bpf_prog_enter_recur(struct bpf_prog *prog, struct bpf_tramp_run_ctx *run_ctx)
__acquires(RCU)
{
rcu_read_lock_dont_migrate();
run_ctx->saved_run_ctx = bpf_set_run_ctx(&run_ctx->run_ctx);
if (unlikely(!bpf_prog_get_recursion_context(prog))) {
bpf_prog_inc_misses_counter(prog);
if (prog->aux->recursion_detected)
prog->aux->recursion_detected(prog);
return 0;
}
return bpf_prog_start_time();
}
static void notrace __update_prog_stats(struct bpf_prog *prog, u64 start)
{
struct bpf_prog_stats *stats;
unsigned long flags;
u64 duration;
/*
* static_key could be enabled in __bpf_prog_enter* and disabled in
* __bpf_prog_exit*. And vice versa. Check that 'start' is valid.
*/
if (start <= NO_START_TIME)
return;
duration = sched_clock() - start;
stats = this_cpu_ptr(prog->stats);
flags = u64_stats_update_begin_irqsave(&stats->syncp);
u64_stats_inc(&stats->cnt);
u64_stats_add(&stats->nsecs, duration);
u64_stats_update_end_irqrestore(&stats->syncp, flags);
}
static __always_inline void notrace update_prog_stats(struct bpf_prog *prog,
u64 start)
{
if (static_branch_unlikely(&bpf_stats_enabled_key))
__update_prog_stats(prog, start);
}
static void notrace __bpf_prog_exit_recur(struct bpf_prog *prog, u64 start,
struct bpf_tramp_run_ctx *run_ctx)
__releases(RCU)
{
bpf_reset_run_ctx(run_ctx->saved_run_ctx);
update_prog_stats(prog, start);
bpf_prog_put_recursion_context(prog);
rcu_read_unlock_migrate();
}
static u64 notrace __bpf_prog_enter_lsm_cgroup(struct bpf_prog *prog,
struct bpf_tramp_run_ctx *run_ctx)
__acquires(RCU)
{
/* Runtime stats are exported via actual BPF_LSM_CGROUP
* programs, not the shims.
*/
rcu_read_lock_dont_migrate();
run_ctx->saved_run_ctx = bpf_set_run_ctx(&run_ctx->run_ctx);
return NO_START_TIME;
}
static void notrace __bpf_prog_exit_lsm_cgroup(struct bpf_prog *prog, u64 start,
struct bpf_tramp_run_ctx *run_ctx)
__releases(RCU)
{
bpf_reset_run_ctx(run_ctx->saved_run_ctx);
rcu_read_unlock_migrate();
}
u64 notrace __bpf_prog_enter_sleepable_recur(struct bpf_prog *prog,
struct bpf_tramp_run_ctx *run_ctx)
{
rcu_read_lock_trace();
migrate_disable();
might_fault();
run_ctx->saved_run_ctx = bpf_set_run_ctx(&run_ctx->run_ctx);
if (unlikely(!bpf_prog_get_recursion_context(prog))) {
bpf_prog_inc_misses_counter(prog);
if (prog->aux->recursion_detected)
prog->aux->recursion_detected(prog);
return 0;
}
return bpf_prog_start_time();
}
void notrace __bpf_prog_exit_sleepable_recur(struct bpf_prog *prog, u64 start,
struct bpf_tramp_run_ctx *run_ctx)
{
bpf_reset_run_ctx(run_ctx->saved_run_ctx);
update_prog_stats(prog, start);
bpf_prog_put_recursion_context(prog);
migrate_enable();
rcu_read_unlock_trace();
}
static u64 notrace __bpf_prog_enter_sleepable(struct bpf_prog *prog,
struct bpf_tramp_run_ctx *run_ctx)
{
rcu_read_lock_trace();
migrate_disable();
might_fault();
run_ctx->saved_run_ctx = bpf_set_run_ctx(&run_ctx->run_ctx);
return bpf_prog_start_time();
}
static void notrace __bpf_prog_exit_sleepable(struct bpf_prog *prog, u64 start,
struct bpf_tramp_run_ctx *run_ctx)
{
bpf_reset_run_ctx(run_ctx->saved_run_ctx);
update_prog_stats(prog, start);
migrate_enable();
rcu_read_unlock_trace();
}
static u64 notrace __bpf_prog_enter(struct bpf_prog *prog,
struct bpf_tramp_run_ctx *run_ctx)
__acquires(RCU)
{
rcu_read_lock_dont_migrate();
run_ctx->saved_run_ctx = bpf_set_run_ctx(&run_ctx->run_ctx);
return bpf_prog_start_time();
}
static void notrace __bpf_prog_exit(struct bpf_prog *prog, u64 start,
struct bpf_tramp_run_ctx *run_ctx)
__releases(RCU)
{
bpf_reset_run_ctx(run_ctx->saved_run_ctx);
update_prog_stats(prog, start);
rcu_read_unlock_migrate();
}
void notrace __bpf_tramp_enter(struct bpf_tramp_image *tr)
{
percpu_ref_get(&tr->pcref);
}
void notrace __bpf_tramp_exit(struct bpf_tramp_image *tr)
{
percpu_ref_put(&tr->pcref);
}
bpf_trampoline_enter_t bpf_trampoline_enter(const struct bpf_prog *prog)
{
bool sleepable = prog->sleepable;
if (bpf_prog_check_recur(prog))
return sleepable ? __bpf_prog_enter_sleepable_recur :
__bpf_prog_enter_recur;
if (resolve_prog_type(prog) == BPF_PROG_TYPE_LSM &&
prog->expected_attach_type == BPF_LSM_CGROUP)
return __bpf_prog_enter_lsm_cgroup;
return sleepable ? __bpf_prog_enter_sleepable : __bpf_prog_enter;
}
bpf_trampoline_exit_t bpf_trampoline_exit(const struct bpf_prog *prog)
{
bool sleepable = prog->sleepable;
if (bpf_prog_check_recur(prog))
return sleepable ? __bpf_prog_exit_sleepable_recur :
__bpf_prog_exit_recur;
if (resolve_prog_type(prog) == BPF_PROG_TYPE_LSM &&
prog->expected_attach_type == BPF_LSM_CGROUP)
return __bpf_prog_exit_lsm_cgroup;
return sleepable ? __bpf_prog_exit_sleepable : __bpf_prog_exit;
}
int __weak
arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *image_end,
const struct btf_func_model *m, u32 flags,
struct bpf_tramp_nodes *tnodes,
void *func_addr)
{
return -ENOTSUPP;
}
void * __weak arch_alloc_bpf_trampoline(unsigned int size)
{
void *image;
if (WARN_ON_ONCE(size > PAGE_SIZE))
return NULL;
image = bpf_jit_alloc_exec(PAGE_SIZE);
if (image)
set_vm_flush_reset_perms(image);
return image;
}
void __weak arch_free_bpf_trampoline(void *image, unsigned int size)
{
WARN_ON_ONCE(size > PAGE_SIZE);
/* bpf_jit_free_exec doesn't need "size", but
* bpf_prog_pack_free() needs it.
*/
bpf_jit_free_exec(image);
}
int __weak arch_protect_bpf_trampoline(void *image, unsigned int size)
{
WARN_ON_ONCE(size > PAGE_SIZE);
return set_memory_rox((long)image, 1);
}
int __weak arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,
struct bpf_tramp_nodes *tnodes, void *func_addr)
{
return -ENOTSUPP;
}
#if defined(CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS) && \
defined(CONFIG_HAVE_SINGLE_FTRACE_DIRECT_OPS) && \
defined(CONFIG_BPF_SYSCALL)
static void trampoline_lock_all(void)
{
int i;
for (i = 0; i < TRAMPOLINE_LOCKS_TABLE_SIZE; i++)
mutex_lock(&trampoline_locks[i].mutex);
}
static void trampoline_unlock_all(void)
{
int i;
for (i = 0; i < TRAMPOLINE_LOCKS_TABLE_SIZE; i++)
mutex_unlock(&trampoline_locks[i].mutex);
}
static void remove_tracing_multi_data(struct bpf_tracing_multi_data *data)
{
ftrace_hash_remove(data->reg);
ftrace_hash_remove(data->unreg);
ftrace_hash_remove(data->modify);
}
static void clear_tracing_multi_data(struct bpf_tracing_multi_data *data)
{
remove_tracing_multi_data(data);
free_ftrace_hash(data->reg);
free_ftrace_hash(data->unreg);
free_ftrace_hash(data->modify);
}
static int init_tracing_multi_data(struct bpf_tracing_multi_data *data)
{
data->reg = alloc_ftrace_hash(FTRACE_HASH_DEFAULT_BITS);
data->unreg = alloc_ftrace_hash(FTRACE_HASH_DEFAULT_BITS);
data->modify = alloc_ftrace_hash(FTRACE_HASH_DEFAULT_BITS);
if (!data->reg || !data->unreg || !data->modify) {
clear_tracing_multi_data(data);
return -ENOMEM;
}
return 0;
}
static void ftrace_hash_add(struct ftrace_hash *hash, struct ftrace_func_entry *entry,
unsigned long ip, unsigned long direct)
{
entry->ip = ip;
entry->direct = direct;
add_ftrace_hash_entry(hash, entry);
}
static int register_fentry_multi(struct bpf_trampoline *tr, struct bpf_tramp_image *im, void *ptr)
{
unsigned long addr = (unsigned long) im->image;
unsigned long ip = ftrace_location(tr->ip);
struct bpf_tracing_multi_data *data = ptr;
if (bpf_trampoline_use_jmp(tr->flags))
addr = ftrace_jmp_set(addr);
tr->func.ftrace_managed = true;
ftrace_hash_add(data->reg, data->entry, ip, addr);
tr->cur_image = im;
return 0;
}
static int unregister_fentry_multi(struct bpf_trampoline *tr, u32 orig_flags, void *ptr)
{
unsigned long addr = (unsigned long) tr->cur_image->image;
unsigned long ip = ftrace_location(tr->ip);
struct bpf_tracing_multi_data *data = ptr;
if (bpf_trampoline_use_jmp(tr->flags))
addr = ftrace_jmp_set(addr);
ftrace_hash_add(data->unreg, data->entry, ip, addr);
tr->cur_image = NULL;
return 0;
}
static int modify_fentry_multi(struct bpf_trampoline *tr, u32 orig_flags, struct bpf_tramp_image *im,
bool lock_direct_mutex, void *ptr)
{
unsigned long addr = (unsigned long) im->image;
unsigned long ip = ftrace_location(tr->ip);
struct bpf_tracing_multi_data *data = ptr;
if (bpf_trampoline_use_jmp(tr->flags))
addr = ftrace_jmp_set(addr);
ftrace_hash_add(data->modify, data->entry, ip, addr);
tr->cur_image = im;
return 0;
}
static const struct bpf_trampoline_ops trampoline_multi_ops = {
.register_fentry = register_fentry_multi,
.unregister_fentry = unregister_fentry_multi,
.modify_fentry = modify_fentry_multi,
};
static void bpf_trampoline_multi_attach_init(struct bpf_trampoline *tr)
{
tr->multi_attach.old_image = tr->cur_image;
tr->multi_attach.old_flags = tr->flags;
}
static void bpf_trampoline_multi_attach_free(struct bpf_trampoline *tr)
{
/*
* Only free old_image if it is no longer the active image.
* When bpf_trampoline_update() fails before modify_fentry_multi()/
* unregister_fentry_multi() is called, cur_image is unchanged
* (cur_image == old_image) and ftrace still points to it. Freeing
* it would cause a UAF when ftrace calls into the freed memory.
* On success, cur_image is either a new image or NULL, so
* old_image != cur_image means the image is stale.
*/
if (tr->multi_attach.old_image &&
tr->multi_attach.old_image != tr->cur_image)
bpf_tramp_image_put(tr->multi_attach.old_image);
tr->multi_attach.old_image = NULL;
tr->multi_attach.old_flags = 0;
}
static void bpf_trampoline_multi_attach_rollback(struct bpf_trampoline *tr)
{
if (tr->cur_image)
bpf_tramp_image_put(tr->cur_image);
tr->cur_image = tr->multi_attach.old_image;
tr->flags = tr->multi_attach.old_flags;
tr->multi_attach.old_image = NULL;
tr->multi_attach.old_flags = 0;
}
#define for_each_mnode_cnt(mnode, link, cnt) \
for (i = 0, mnode = &link->nodes[i]; i < cnt; i++, mnode = &link->nodes[i])
#define for_each_mnode(mnode, link) \
for_each_mnode_cnt(mnode, link, link->nodes_cnt)
int bpf_trampoline_multi_attach(struct bpf_prog *prog, u32 *ids,
struct bpf_tracing_multi_link *link)
{
struct bpf_tracing_multi_data *data = &link->data;
struct bpf_attach_target_info tgt_info = {};
struct btf *btf = prog->aux->attach_btf;
struct bpf_tracing_multi_node *mnode;
struct bpf_trampoline *tr;
int i, err, rollback_cnt;
u64 key;
for_each_mnode(mnode, link) {
rollback_cnt = i;
err = bpf_check_attach_btf_id_multi(btf, prog, ids[i], &tgt_info);
if (err)
goto rollback_put;
key = bpf_trampoline_compute_key(NULL, btf, ids[i]);
tr = bpf_trampoline_get(key, &tgt_info);
if (!tr) {
err = -ENOMEM;
goto rollback_put;
}
mnode->trampoline = tr;
mnode->node.link = &link->link;
mnode->node.cookie = link->cookies ? link->cookies[i] : 0;
if (prog->expected_attach_type == BPF_TRACE_FSESSION_MULTI) {
link->fexits[i].link = &link->link;
link->fexits[i].cookie = link->cookies ? link->cookies[i] : 0;
}
cond_resched();
}
err = init_tracing_multi_data(data);
if (err) {
rollback_cnt = link->nodes_cnt;
goto rollback_put;
}
trampoline_lock_all();
for_each_mnode(mnode, link) {
bpf_trampoline_multi_attach_init(mnode->trampoline);
data->entry = &mnode->entry;
err = __bpf_trampoline_link_prog(&mnode->node, mnode->trampoline, NULL,
&trampoline_multi_ops, data);
if (err) {
rollback_cnt = i;
goto rollback_unlink;
}
}
rollback_cnt = link->nodes_cnt;
if (ftrace_hash_count(data->reg)) {
err = update_ftrace_direct_add(&direct_ops, data->reg);
if (err)
goto rollback_unlink;
}
if (ftrace_hash_count(data->modify)) {
err = update_ftrace_direct_mod(&direct_ops, data->modify, true);
if (err) {
if (ftrace_hash_count(data->reg))
WARN_ON_ONCE(update_ftrace_direct_del(&direct_ops, data->reg));
goto rollback_unlink;
}
}
for_each_mnode(mnode, link)
bpf_trampoline_multi_attach_free(mnode->trampoline);
trampoline_unlock_all();
remove_tracing_multi_data(data);
return 0;
rollback_unlink:
for_each_mnode_cnt(mnode, link, rollback_cnt) {
bpf_trampoline_remove_prog(mnode->trampoline, &mnode->node);
bpf_trampoline_multi_attach_rollback(mnode->trampoline);
}
trampoline_unlock_all();
clear_tracing_multi_data(data);
rollback_cnt = link->nodes_cnt;
rollback_put:
for_each_mnode_cnt(mnode, link, rollback_cnt)
bpf_trampoline_put(mnode->trampoline);
return err;
}
void bpf_trampoline_multi_detach(struct bpf_prog *prog,
struct bpf_tracing_multi_link *link)
{
struct bpf_tracing_multi_data *data = &link->data;
struct bpf_tracing_multi_node *mnode;
int i, err;
trampoline_lock_all();
for_each_mnode(mnode, link) {
data->entry = &mnode->entry;
bpf_trampoline_multi_attach_init(mnode->trampoline);
err = __bpf_trampoline_unlink_prog(&mnode->node, mnode->trampoline, NULL,
&trampoline_multi_ops, data);
WARN_ONCE(err, "__bpf_trampoline_unlink_prog failed: %d\n", err);
}
if (ftrace_hash_count(data->unreg))
WARN_ON_ONCE(update_ftrace_direct_del(&direct_ops, data->unreg));
if (ftrace_hash_count(data->modify))
WARN_ON_ONCE(update_ftrace_direct_mod(&direct_ops, data->modify, true));
for_each_mnode(mnode, link)
bpf_trampoline_multi_attach_free(mnode->trampoline);
trampoline_unlock_all();
for_each_mnode(mnode, link)
bpf_trampoline_put(mnode->trampoline);
clear_tracing_multi_data(data);
}
#undef for_each_mnode_cnt
#undef for_each_mnode
#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS &&
CONFIG_HAVE_SINGLE_FTRACE_DIRECT_OPS &&
CONFIG_BPF_SYSCALL */
static int __init init_trampolines(void)
{
int i;
for (i = 0; i < TRAMPOLINE_TABLE_SIZE; i++)
INIT_HLIST_HEAD(&trampoline_key_table[i]);
for (i = 0; i < TRAMPOLINE_TABLE_SIZE; i++)
INIT_HLIST_HEAD(&trampoline_ip_table[i]);
for (i = 0; i < TRAMPOLINE_LOCKS_TABLE_SIZE; i++)
__mutex_init(&trampoline_locks[i].mutex, "trampoline_lock", &trampoline_locks[i].key);
return 0;
}
late_initcall(init_trampolines);