Message ID | 160051618622.58048.13304507277053169557.stgit@toke.dk |
---|---|
State | Changes Requested |
Delegated to: | BPF Maintainers |
Headers | show |
Series | bpf: Support multi-attach for freplace programs | expand |
On Sat, Sep 19, 2020 at 4:50 AM Toke Høiland-Jørgensen <toke@redhat.com> wrote: > > From: Toke Høiland-Jørgensen <toke@redhat.com> > > The check_attach_btf_id() function really does three things: > > 1. It performs a bunch of checks on the program to ensure that the > attachment is valid. > > 2. It stores a bunch of state about the attachment being requested in > the verifier environment and struct bpf_prog objects. > > 3. It allocates a trampoline for the attachment. > > This patch splits out (1.) and (3.) into separate functions in preparation > for reusing them when the actual attachment is happening (in the > raw_tracepoint_open syscall operation), which will allow tracing programs > to have multiple (compatible) attachments. > > No functional change is intended with this patch. > > Acked-by: Andrii Nakryiko <andriin@fb.com> > Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com> > --- Ok, so bad news: you broke another selftest (test_overhead). Please, do run test_progs and make sure everything succeeds, every time before you post a new version. Good news, though, is that this refactoring actually fixed a pretty nasty bug in check_attach_btf_id logic: whenever bpf_trampoline already existed (e.g., due to successful fentry to function X), all subsequent fentry/fexit/fmod_ret and all their sleepable variants would skip a bunch of check. So please attach the following Fixes tags: Fixes: 6ba43b761c41 ("bpf: Attachment verification for BPF_MODIFY_RETURN") Fixes: 1e6c62a88215 ("bpf: Introduce sleepable BPF programs") As for selftests, feel free to just drop the fmod_ret program, it was never supposed to be possible, I just never realized that. > include/linux/bpf.h | 7 + > include/linux/bpf_verifier.h | 9 ++ > kernel/bpf/trampoline.c | 20 ++++ > kernel/bpf/verifier.c | 197 ++++++++++++++++++++++++------------------ > 4 files changed, 149 insertions(+), 84 deletions(-) > [...]
Andrii Nakryiko <andrii.nakryiko@gmail.com> writes: > On Sat, Sep 19, 2020 at 4:50 AM Toke Høiland-Jørgensen <toke@redhat.com> wrote: >> >> From: Toke Høiland-Jørgensen <toke@redhat.com> >> >> The check_attach_btf_id() function really does three things: >> >> 1. It performs a bunch of checks on the program to ensure that the >> attachment is valid. >> >> 2. It stores a bunch of state about the attachment being requested in >> the verifier environment and struct bpf_prog objects. >> >> 3. It allocates a trampoline for the attachment. >> >> This patch splits out (1.) and (3.) into separate functions in preparation >> for reusing them when the actual attachment is happening (in the >> raw_tracepoint_open syscall operation), which will allow tracing programs >> to have multiple (compatible) attachments. >> >> No functional change is intended with this patch. >> >> Acked-by: Andrii Nakryiko <andriin@fb.com> >> Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com> >> --- > > Ok, so bad news: you broke another selftest (test_overhead). Please, > do run test_progs and make sure everything succeeds, every time before > you post a new version. Ah, right, I'll take a look. I have had some trouble with running all the tests because my VM runs out of memory, so I was picking and choosing. Guess I'll go and actually fix this and run the full test suite... > Good news, though, is that this refactoring actually fixed a pretty > nasty bug in check_attach_btf_id logic: whenever bpf_trampoline > already existed (e.g., due to successful fentry to function X), all > subsequent fentry/fexit/fmod_ret and all their sleepable variants > would skip a bunch of check. So please attach the following Fixes > tags: > > Fixes: 6ba43b761c41 ("bpf: Attachment verification for BPF_MODIFY_RETURN") > Fixes: 1e6c62a88215 ("bpf: Introduce sleepable BPF programs") Ah, yes, well spotted! Will add the Fixes tags :) > As for selftests, feel free to just drop the fmod_ret program, it was > never supposed to be possible, I just never realized that. Well, doesn't hurt to have it, does it? If you don't mind I think I'll just keep it... -Toke
Andrii Nakryiko <andrii.nakryiko@gmail.com> writes: > On Sat, Sep 19, 2020 at 4:50 AM Toke Høiland-Jørgensen <toke@redhat.com> wrote: >> >> From: Toke Høiland-Jørgensen <toke@redhat.com> >> >> The check_attach_btf_id() function really does three things: >> >> 1. It performs a bunch of checks on the program to ensure that the >> attachment is valid. >> >> 2. It stores a bunch of state about the attachment being requested in >> the verifier environment and struct bpf_prog objects. >> >> 3. It allocates a trampoline for the attachment. >> >> This patch splits out (1.) and (3.) into separate functions in preparation >> for reusing them when the actual attachment is happening (in the >> raw_tracepoint_open syscall operation), which will allow tracing programs >> to have multiple (compatible) attachments. >> >> No functional change is intended with this patch. >> >> Acked-by: Andrii Nakryiko <andriin@fb.com> >> Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com> >> --- > > Ok, so bad news: you broke another selftest (test_overhead). Please, > do run test_progs and make sure everything succeeds, every time before > you post a new version. Right, so I looked into this, and it seems the only reason it was succeeding before were those skipped checks you pointed out that are now fixed. I.e., __set_task_comm() is not actually supposed to be fmod_ret'able according to check_attach_modify_return(). So I'm not sure what the right way to fix this is? The fmod_ret bit was added to test_overhead by: 4eaf0b5c5e04 ("selftest/bpf: Fmod_ret prog and implement test_overhead as part of bench") so the obvious thing is to just do a (partial) revert of that? WDYT? -Toke
On Tue, Sep 22, 2020 at 4:16 AM Toke Høiland-Jørgensen <toke@redhat.com> wrote: > > Andrii Nakryiko <andrii.nakryiko@gmail.com> writes: > > > On Sat, Sep 19, 2020 at 4:50 AM Toke Høiland-Jørgensen <toke@redhat.com> wrote: > >> > >> From: Toke Høiland-Jørgensen <toke@redhat.com> > >> > >> The check_attach_btf_id() function really does three things: > >> > >> 1. It performs a bunch of checks on the program to ensure that the > >> attachment is valid. > >> > >> 2. It stores a bunch of state about the attachment being requested in > >> the verifier environment and struct bpf_prog objects. > >> > >> 3. It allocates a trampoline for the attachment. > >> > >> This patch splits out (1.) and (3.) into separate functions in preparation > >> for reusing them when the actual attachment is happening (in the > >> raw_tracepoint_open syscall operation), which will allow tracing programs > >> to have multiple (compatible) attachments. > >> > >> No functional change is intended with this patch. > >> > >> Acked-by: Andrii Nakryiko <andriin@fb.com> > >> Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com> > >> --- > > > > Ok, so bad news: you broke another selftest (test_overhead). Please, > > do run test_progs and make sure everything succeeds, every time before > > you post a new version. > > Right, so I looked into this, and it seems the only reason it was > succeeding before were those skipped checks you pointed out that are now > fixed. I.e., __set_task_comm() is not actually supposed to be > fmod_ret'able according to check_attach_modify_return(). So I'm not sure > what the right way to fix this is? You have to remove the fmod_ret part from test_overhead, it was never supposed to work. > > The fmod_ret bit was added to test_overhead by: > > 4eaf0b5c5e04 ("selftest/bpf: Fmod_ret prog and implement test_overhead as part of bench") > > so the obvious thing is to just do a (partial) revert of that? WDYT? > > -Toke >
Andrii Nakryiko <andrii.nakryiko@gmail.com> writes: > On Tue, Sep 22, 2020 at 4:16 AM Toke Høiland-Jørgensen <toke@redhat.com> wrote: >> >> Andrii Nakryiko <andrii.nakryiko@gmail.com> writes: >> >> > On Sat, Sep 19, 2020 at 4:50 AM Toke Høiland-Jørgensen <toke@redhat.com> wrote: >> >> >> >> From: Toke Høiland-Jørgensen <toke@redhat.com> >> >> >> >> The check_attach_btf_id() function really does three things: >> >> >> >> 1. It performs a bunch of checks on the program to ensure that the >> >> attachment is valid. >> >> >> >> 2. It stores a bunch of state about the attachment being requested in >> >> the verifier environment and struct bpf_prog objects. >> >> >> >> 3. It allocates a trampoline for the attachment. >> >> >> >> This patch splits out (1.) and (3.) into separate functions in preparation >> >> for reusing them when the actual attachment is happening (in the >> >> raw_tracepoint_open syscall operation), which will allow tracing programs >> >> to have multiple (compatible) attachments. >> >> >> >> No functional change is intended with this patch. >> >> >> >> Acked-by: Andrii Nakryiko <andriin@fb.com> >> >> Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com> >> >> --- >> > >> > Ok, so bad news: you broke another selftest (test_overhead). Please, >> > do run test_progs and make sure everything succeeds, every time before >> > you post a new version. >> >> Right, so I looked into this, and it seems the only reason it was >> succeeding before were those skipped checks you pointed out that are now >> fixed. I.e., __set_task_comm() is not actually supposed to be >> fmod_ret'able according to check_attach_modify_return(). So I'm not sure >> what the right way to fix this is? > > You have to remove the fmod_ret part from test_overhead, it was never > supposed to work. Right, sure, that I can do :) -Toke
diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 59db5165b3d8..9d444021f160 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -616,6 +616,8 @@ static __always_inline unsigned int bpf_dispatcher_nop_func( struct bpf_trampoline *bpf_trampoline_lookup(u64 key); int bpf_trampoline_link_prog(struct bpf_prog *prog); int bpf_trampoline_unlink_prog(struct bpf_prog *prog); +struct bpf_trampoline *bpf_trampoline_get(u64 key, void *addr, + struct btf_func_model *fmodel); void bpf_trampoline_put(struct bpf_trampoline *tr); #define BPF_DISPATCHER_INIT(_name) { \ .mutex = __MUTEX_INITIALIZER(_name.mutex), \ @@ -672,6 +674,11 @@ static inline int bpf_trampoline_unlink_prog(struct bpf_prog *prog) { return -ENOTSUPP; } +static inline struct bpf_trampoline *bpf_trampoline_get(u64 key, void *addr, + struct btf_func_model *fmodel) +{ + return ERR_PTR(-EOPNOTSUPP); +} static inline void bpf_trampoline_put(struct bpf_trampoline *tr) {} #define DEFINE_BPF_DISPATCHER(name) #define DECLARE_BPF_DISPATCHER(name) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 7bc9276c4ef4..4fe718a5b4cd 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -450,4 +450,13 @@ bpf_prog_offload_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt); int check_ctx_reg(struct bpf_verifier_env *env, const struct bpf_reg_state *reg, int regno); +int bpf_check_attach_target(struct bpf_verifier_log *log, + const struct bpf_prog *prog, + const struct bpf_prog *tgt_prog, + u32 btf_id, + struct btf_func_model *fmodel, + long *tgt_addr, + const char **tgt_name, + const struct btf_type **tgt_type); + #endif /* _LINUX_BPF_VERIFIER_H */ diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c index 7dd523a7e32d..e86d32f7f7dc 100644 --- a/kernel/bpf/trampoline.c +++ b/kernel/bpf/trampoline.c @@ -336,6 +336,26 @@ int bpf_trampoline_unlink_prog(struct bpf_prog *prog) return err; } +struct bpf_trampoline *bpf_trampoline_get(u64 key, void *addr, + struct btf_func_model *fmodel) +{ + struct bpf_trampoline *tr; + + tr = bpf_trampoline_lookup(key); + if (!tr) + return NULL; + + mutex_lock(&tr->mutex); + if (tr->func.addr) + goto out; + + memcpy(&tr->func.model, fmodel, sizeof(*fmodel)); + tr->func.addr = addr; +out: + mutex_unlock(&tr->mutex); + return tr; +} + void bpf_trampoline_put(struct bpf_trampoline *tr) { if (!tr) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 44cfd5697241..412b0810807f 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -11144,11 +11144,11 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env) } #define SECURITY_PREFIX "security_" -static int check_attach_modify_return(struct bpf_prog *prog, unsigned long addr) +static int check_attach_modify_return(const struct bpf_prog *prog, unsigned long addr, + const char *func_name) { if (within_error_injection_list(addr) || - !strncmp(SECURITY_PREFIX, prog->aux->attach_func_name, - sizeof(SECURITY_PREFIX) - 1)) + !strncmp(SECURITY_PREFIX, func_name, sizeof(SECURITY_PREFIX) - 1)) return 0; return -EINVAL; @@ -11185,43 +11185,29 @@ static int check_non_sleepable_error_inject(u32 btf_id) return btf_id_set_contains(&btf_non_sleepable_error_inject, btf_id); } -static int check_attach_btf_id(struct bpf_verifier_env *env) +int bpf_check_attach_target(struct bpf_verifier_log *log, + const struct bpf_prog *prog, + const struct bpf_prog *tgt_prog, + u32 btf_id, + struct btf_func_model *fmodel, + long *tgt_addr, + const char **tgt_name, + const struct btf_type **tgt_type) { - struct bpf_prog *prog = env->prog; bool prog_extension = prog->type == BPF_PROG_TYPE_EXT; - struct bpf_prog *tgt_prog = prog->aux->linked_prog; - struct bpf_verifier_log *log = &env->log; - u32 btf_id = prog->aux->attach_btf_id; const char prefix[] = "btf_trace_"; - struct btf_func_model fmodel; int ret = 0, subprog = -1, i; - struct bpf_trampoline *tr; const struct btf_type *t; bool conservative = true; const char *tname; struct btf *btf; - long addr; - u64 key; - - if (prog->aux->sleepable && prog->type != BPF_PROG_TYPE_TRACING && - prog->type != BPF_PROG_TYPE_LSM) { - verbose(env, "Only fentry/fexit/fmod_ret and lsm programs can be sleepable\n"); - return -EINVAL; - } - - if (prog->type == BPF_PROG_TYPE_STRUCT_OPS) - return check_struct_ops_btf_id(env); - - if (prog->type != BPF_PROG_TYPE_TRACING && - prog->type != BPF_PROG_TYPE_LSM && - !prog_extension) - return 0; + long addr = 0; if (!btf_id) { bpf_log(log, "Tracing programs must provide btf_id\n"); return -EINVAL; } - btf = bpf_prog_get_target_btf(prog); + btf = tgt_prog ? tgt_prog->aux->btf : btf_vmlinux; if (!btf) { bpf_log(log, "FENTRY/FEXIT program can only be attached to another program annotated with BTF\n"); @@ -11261,8 +11247,6 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) "Extension programs should be JITed\n"); return -EINVAL; } - env->ops = bpf_verifier_ops[tgt_prog->type]; - prog->expected_attach_type = tgt_prog->expected_attach_type; } if (!tgt_prog->jited) { bpf_log(log, "Can attach to only JITed progs\n"); @@ -11298,13 +11282,11 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) bpf_log(log, "Cannot extend fentry/fexit\n"); return -EINVAL; } - key = ((u64)aux->id) << 32 | btf_id; } else { if (prog_extension) { bpf_log(log, "Cannot replace kernel functions\n"); return -EINVAL; } - key = btf_id; } switch (prog->expected_attach_type) { @@ -11334,13 +11316,7 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) /* should never happen in valid vmlinux build */ return -EINVAL; - /* remember two read only pointers that are valid for - * the life time of the kernel - */ - prog->aux->attach_func_name = tname; - prog->aux->attach_func_proto = t; - prog->aux->attach_btf_trace = true; - return 0; + break; case BPF_TRACE_ITER: if (!btf_type_is_func(t)) { bpf_log(log, "attach_btf_id %u is not a function\n", @@ -11350,12 +11326,10 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) t = btf_type_by_id(btf, t->type); if (!btf_type_is_func_proto(t)) return -EINVAL; - prog->aux->attach_func_name = tname; - prog->aux->attach_func_proto = t; - if (!bpf_iter_prog_supported(prog)) - return -EINVAL; - ret = btf_distill_func_proto(log, btf, t, tname, &fmodel); - return ret; + ret = btf_distill_func_proto(log, btf, t, tname, fmodel); + if (ret) + return ret; + break; default: if (!prog_extension) return -EINVAL; @@ -11364,13 +11338,6 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) case BPF_LSM_MAC: case BPF_TRACE_FENTRY: case BPF_TRACE_FEXIT: - prog->aux->attach_func_name = tname; - if (prog->type == BPF_PROG_TYPE_LSM) { - ret = bpf_lsm_verify_prog(log, prog); - if (ret < 0) - return ret; - } - if (!btf_type_is_func(t)) { bpf_log(log, "attach_btf_id %u is not a function\n", btf_id); @@ -11382,24 +11349,14 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) t = btf_type_by_id(btf, t->type); if (!btf_type_is_func_proto(t)) return -EINVAL; - tr = bpf_trampoline_lookup(key); - if (!tr) - return -ENOMEM; - /* t is either vmlinux type or another program's type */ - prog->aux->attach_func_proto = t; - mutex_lock(&tr->mutex); - if (tr->func.addr) { - prog->aux->trampoline = tr; - goto out; - } - if (tgt_prog && conservative) { - prog->aux->attach_func_proto = NULL; + + if (tgt_prog && conservative) t = NULL; - } - ret = btf_distill_func_proto(log, btf, t, - tname, &tr->func.model); + + ret = btf_distill_func_proto(log, btf, t, tname, fmodel); if (ret < 0) - goto out; + return ret; + if (tgt_prog) { if (subprog == 0) addr = (long) tgt_prog->bpf_func; @@ -11411,8 +11368,7 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) bpf_log(log, "The address of function %s cannot be found\n", tname); - ret = -ENOENT; - goto out; + return -ENOENT; } } @@ -11437,25 +11393,98 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) default: break; } - if (ret) - bpf_log(log, "%s is not sleepable\n", - prog->aux->attach_func_name); + if (ret) { + bpf_log(log, "%s is not sleepable\n", tname); + return ret; + } } else if (prog->expected_attach_type == BPF_MODIFY_RETURN) { - ret = check_attach_modify_return(prog, addr); - if (ret || tgt_prog) - bpf_log(log, "%s() is not modifiable\n", - prog->aux->attach_func_name); + ret = check_attach_modify_return(prog, addr, tname); + if (ret || tgt_prog) { + bpf_log(log, "%s() is not modifiable\n", tname); + return ret; + } } - if (ret) - goto out; - tr->func.addr = (void *)addr; - prog->aux->trampoline = tr; -out: - mutex_unlock(&tr->mutex); - if (ret) - bpf_trampoline_put(tr); + + break; + } + *tgt_addr = addr; + if (tgt_name) + *tgt_name = tname; + if (tgt_type) + *tgt_type = t; + return 0; +} + +static int check_attach_btf_id(struct bpf_verifier_env *env) +{ + struct bpf_prog *prog = env->prog; + struct bpf_prog *tgt_prog = prog->aux->linked_prog; + u32 btf_id = prog->aux->attach_btf_id; + struct btf_func_model fmodel; + struct bpf_trampoline *tr; + const struct btf_type *t; + const char *tname; + long addr; + int ret; + u64 key; + + if (prog->aux->sleepable && prog->type != BPF_PROG_TYPE_TRACING && + prog->type != BPF_PROG_TYPE_LSM) { + verbose(env, "Only fentry/fexit/fmod_ret and lsm programs can be sleepable\n"); + return -EINVAL; + } + + if (prog->type == BPF_PROG_TYPE_STRUCT_OPS) + return check_struct_ops_btf_id(env); + + if (prog->type != BPF_PROG_TYPE_TRACING && + prog->type != BPF_PROG_TYPE_LSM && + prog->type != BPF_PROG_TYPE_EXT) + return 0; + + ret = bpf_check_attach_target(&env->log, prog, tgt_prog, btf_id, + &fmodel, &addr, &tname, &t); + if (ret) return ret; + + if (tgt_prog) { + if (prog->type == BPF_PROG_TYPE_EXT) { + env->ops = bpf_verifier_ops[tgt_prog->type]; + prog->expected_attach_type = + tgt_prog->expected_attach_type; + } + key = ((u64)tgt_prog->aux->id) << 32 | btf_id; + } else { + key = btf_id; } + + /* remember two read only pointers that are valid for + * the life time of the kernel + */ + prog->aux->attach_func_proto = t; + prog->aux->attach_func_name = tname; + + if (prog->expected_attach_type == BPF_TRACE_RAW_TP) { + prog->aux->attach_btf_trace = true; + return 0; + } else if (prog->expected_attach_type == BPF_TRACE_ITER) { + if (!bpf_iter_prog_supported(prog)) + return -EINVAL; + return 0; + } + + if (prog->type == BPF_PROG_TYPE_LSM) { + ret = bpf_lsm_verify_prog(&env->log, prog); + if (ret < 0) + return ret; + } + + tr = bpf_trampoline_get(key, (void *)addr, &fmodel); + if (!tr) + return -ENOMEM; + + prog->aux->trampoline = tr; + return 0; } int bpf_check(struct bpf_prog **prog, union bpf_attr *attr,