Merge branch 'bpf_modify_ret'
KP Singh says: ==================== v3 -> v4: * Fix a memory leak noticed by Daniel. v2 -> v3: * bpf_trampoline_update_progs -> bpf_trampoline_get_progs + const qualification. * Typos in commit messages. * Added Andrii's Acks. v1 -> v2: * Adressed Andrii's feedback. * Fixed a bug that Alexei noticed about nop generation. * Rebase. This was brought up in the KRSI v4 discussion and found to be useful both for security and tracing programs. https://lore.kernel.org/bpf/20200225193108.GB22391@chromium.org/ The modify_return programs are allowed for security hooks (with an extra CAP_MAC_ADMIN check) and functions whitelisted for error injection (ALLOW_ERROR_INJECTION). The "security_" check is expected to be cleaned up with the KRSI patch series. Here is an example of how a fmod_ret program behaves: int func_to_be_attached(int a, int b) { <--- do_fentry do_fmod_ret: <update ret by calling fmod_ret> if (ret != 0) goto do_fexit; original_function: <side_effects_happen_here> } <--- do_fexit ALLOW_ERROR_INJECTION(func_to_be_attached, ERRNO) The fmod_ret program attached to this function can be defined as: SEC("fmod_ret/func_to_be_attached") int BPF_PROG(func_name, int a, int b, int ret) { // This will skip the original function logic. return -1; } ==================== Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
9ce6010290
arch/x86/net
include
kernel
net/bpf
tools
include/uapi/linux
lib/bpf
testing/selftests/bpf
@ -1361,41 +1361,165 @@ static void restore_regs(const struct btf_func_model *m, u8 **prog, int nr_args,
|
||||
-(stack_size - i * 8));
|
||||
}
|
||||
|
||||
static int invoke_bpf(const struct btf_func_model *m, u8 **pprog,
|
||||
struct bpf_prog **progs, int prog_cnt, int stack_size)
|
||||
static int invoke_bpf_prog(const struct btf_func_model *m, u8 **pprog,
|
||||
struct bpf_prog *p, int stack_size, bool mod_ret)
|
||||
{
|
||||
u8 *prog = *pprog;
|
||||
int cnt = 0, i;
|
||||
int cnt = 0;
|
||||
|
||||
for (i = 0; i < prog_cnt; i++) {
|
||||
if (emit_call(&prog, __bpf_prog_enter, prog))
|
||||
return -EINVAL;
|
||||
/* remember prog start time returned by __bpf_prog_enter */
|
||||
emit_mov_reg(&prog, true, BPF_REG_6, BPF_REG_0);
|
||||
if (emit_call(&prog, __bpf_prog_enter, prog))
|
||||
return -EINVAL;
|
||||
/* remember prog start time returned by __bpf_prog_enter */
|
||||
emit_mov_reg(&prog, true, BPF_REG_6, BPF_REG_0);
|
||||
|
||||
/* arg1: lea rdi, [rbp - stack_size] */
|
||||
EMIT4(0x48, 0x8D, 0x7D, -stack_size);
|
||||
/* arg2: progs[i]->insnsi for interpreter */
|
||||
if (!progs[i]->jited)
|
||||
emit_mov_imm64(&prog, BPF_REG_2,
|
||||
(long) progs[i]->insnsi >> 32,
|
||||
(u32) (long) progs[i]->insnsi);
|
||||
/* call JITed bpf program or interpreter */
|
||||
if (emit_call(&prog, progs[i]->bpf_func, prog))
|
||||
return -EINVAL;
|
||||
/* arg1: lea rdi, [rbp - stack_size] */
|
||||
EMIT4(0x48, 0x8D, 0x7D, -stack_size);
|
||||
/* arg2: progs[i]->insnsi for interpreter */
|
||||
if (!p->jited)
|
||||
emit_mov_imm64(&prog, BPF_REG_2,
|
||||
(long) p->insnsi >> 32,
|
||||
(u32) (long) p->insnsi);
|
||||
/* call JITed bpf program or interpreter */
|
||||
if (emit_call(&prog, p->bpf_func, prog))
|
||||
return -EINVAL;
|
||||
|
||||
/* arg1: mov rdi, progs[i] */
|
||||
emit_mov_imm64(&prog, BPF_REG_1, (long) progs[i] >> 32,
|
||||
(u32) (long) progs[i]);
|
||||
/* arg2: mov rsi, rbx <- start time in nsec */
|
||||
emit_mov_reg(&prog, true, BPF_REG_2, BPF_REG_6);
|
||||
if (emit_call(&prog, __bpf_prog_exit, prog))
|
||||
/* BPF_TRAMP_MODIFY_RETURN trampolines can modify the return
|
||||
* of the previous call which is then passed on the stack to
|
||||
* the next BPF program.
|
||||
*/
|
||||
if (mod_ret)
|
||||
emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -8);
|
||||
|
||||
/* arg1: mov rdi, progs[i] */
|
||||
emit_mov_imm64(&prog, BPF_REG_1, (long) p >> 32,
|
||||
(u32) (long) p);
|
||||
/* arg2: mov rsi, rbx <- start time in nsec */
|
||||
emit_mov_reg(&prog, true, BPF_REG_2, BPF_REG_6);
|
||||
if (emit_call(&prog, __bpf_prog_exit, prog))
|
||||
return -EINVAL;
|
||||
|
||||
*pprog = prog;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void emit_nops(u8 **pprog, unsigned int len)
|
||||
{
|
||||
unsigned int i, noplen;
|
||||
u8 *prog = *pprog;
|
||||
int cnt = 0;
|
||||
|
||||
while (len > 0) {
|
||||
noplen = len;
|
||||
|
||||
if (noplen > ASM_NOP_MAX)
|
||||
noplen = ASM_NOP_MAX;
|
||||
|
||||
for (i = 0; i < noplen; i++)
|
||||
EMIT1(ideal_nops[noplen][i]);
|
||||
len -= noplen;
|
||||
}
|
||||
|
||||
*pprog = prog;
|
||||
}
|
||||
|
||||
static void emit_align(u8 **pprog, u32 align)
|
||||
{
|
||||
u8 *target, *prog = *pprog;
|
||||
|
||||
target = PTR_ALIGN(prog, align);
|
||||
if (target != prog)
|
||||
emit_nops(&prog, target - prog);
|
||||
|
||||
*pprog = prog;
|
||||
}
|
||||
|
||||
static int emit_cond_near_jump(u8 **pprog, void *func, void *ip, u8 jmp_cond)
|
||||
{
|
||||
u8 *prog = *pprog;
|
||||
int cnt = 0;
|
||||
s64 offset;
|
||||
|
||||
offset = func - (ip + 2 + 4);
|
||||
if (!is_simm32(offset)) {
|
||||
pr_err("Target %p is out of range\n", func);
|
||||
return -EINVAL;
|
||||
}
|
||||
EMIT2_off32(0x0F, jmp_cond + 0x10, offset);
|
||||
*pprog = prog;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int emit_mod_ret_check_imm8(u8 **pprog, int value)
|
||||
{
|
||||
u8 *prog = *pprog;
|
||||
int cnt = 0;
|
||||
|
||||
if (!is_imm8(value))
|
||||
return -EINVAL;
|
||||
|
||||
if (value == 0)
|
||||
EMIT2(0x85, add_2reg(0xC0, BPF_REG_0, BPF_REG_0));
|
||||
else
|
||||
EMIT3(0x83, add_1reg(0xF8, BPF_REG_0), value);
|
||||
|
||||
*pprog = prog;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int invoke_bpf(const struct btf_func_model *m, u8 **pprog,
|
||||
struct bpf_tramp_progs *tp, int stack_size)
|
||||
{
|
||||
int i;
|
||||
u8 *prog = *pprog;
|
||||
|
||||
for (i = 0; i < tp->nr_progs; i++) {
|
||||
if (invoke_bpf_prog(m, &prog, tp->progs[i], stack_size, false))
|
||||
return -EINVAL;
|
||||
}
|
||||
*pprog = prog;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int invoke_bpf_mod_ret(const struct btf_func_model *m, u8 **pprog,
|
||||
struct bpf_tramp_progs *tp, int stack_size,
|
||||
u8 **branches)
|
||||
{
|
||||
u8 *prog = *pprog;
|
||||
int i;
|
||||
|
||||
/* The first fmod_ret program will receive a garbage return value.
|
||||
* Set this to 0 to avoid confusing the program.
|
||||
*/
|
||||
emit_mov_imm32(&prog, false, BPF_REG_0, 0);
|
||||
emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -8);
|
||||
for (i = 0; i < tp->nr_progs; i++) {
|
||||
if (invoke_bpf_prog(m, &prog, tp->progs[i], stack_size, true))
|
||||
return -EINVAL;
|
||||
|
||||
/* Generate a branch:
|
||||
*
|
||||
* if (ret != 0)
|
||||
* goto do_fexit;
|
||||
*
|
||||
* If needed this can be extended to any integer value which can
|
||||
* be passed by user-space when the program is loaded.
|
||||
*/
|
||||
if (emit_mod_ret_check_imm8(&prog, 0))
|
||||
return -EINVAL;
|
||||
|
||||
/* Save the location of the branch and Generate 6 nops
|
||||
* (4 bytes for an offset and 2 bytes for the jump) These nops
|
||||
* are replaced with a conditional jump once do_fexit (i.e. the
|
||||
* start of the fexit invocation) is finalized.
|
||||
*/
|
||||
branches[i] = prog;
|
||||
emit_nops(&prog, 4 + 2);
|
||||
}
|
||||
|
||||
*pprog = prog;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Example:
|
||||
* __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev);
|
||||
* its 'struct btf_func_model' will be nr_args=2
|
||||
@ -1458,12 +1582,15 @@ static int invoke_bpf(const struct btf_func_model *m, u8 **pprog,
|
||||
*/
|
||||
int arch_prepare_bpf_trampoline(void *image, void *image_end,
|
||||
const struct btf_func_model *m, u32 flags,
|
||||
struct bpf_prog **fentry_progs, int fentry_cnt,
|
||||
struct bpf_prog **fexit_progs, int fexit_cnt,
|
||||
struct bpf_tramp_progs *tprogs,
|
||||
void *orig_call)
|
||||
{
|
||||
int cnt = 0, nr_args = m->nr_args;
|
||||
int ret, i, cnt = 0, nr_args = m->nr_args;
|
||||
int stack_size = nr_args * 8;
|
||||
struct bpf_tramp_progs *fentry = &tprogs[BPF_TRAMP_FENTRY];
|
||||
struct bpf_tramp_progs *fexit = &tprogs[BPF_TRAMP_FEXIT];
|
||||
struct bpf_tramp_progs *fmod_ret = &tprogs[BPF_TRAMP_MODIFY_RETURN];
|
||||
u8 **branches = NULL;
|
||||
u8 *prog;
|
||||
|
||||
/* x86-64 supports up to 6 arguments. 7+ can be added in the future */
|
||||
@ -1492,28 +1619,64 @@ int arch_prepare_bpf_trampoline(void *image, void *image_end,
|
||||
|
||||
save_regs(m, &prog, nr_args, stack_size);
|
||||
|
||||
if (fentry_cnt)
|
||||
if (invoke_bpf(m, &prog, fentry_progs, fentry_cnt, stack_size))
|
||||
if (fentry->nr_progs)
|
||||
if (invoke_bpf(m, &prog, fentry, stack_size))
|
||||
return -EINVAL;
|
||||
|
||||
if (fmod_ret->nr_progs) {
|
||||
branches = kcalloc(fmod_ret->nr_progs, sizeof(u8 *),
|
||||
GFP_KERNEL);
|
||||
if (!branches)
|
||||
return -ENOMEM;
|
||||
|
||||
if (invoke_bpf_mod_ret(m, &prog, fmod_ret, stack_size,
|
||||
branches)) {
|
||||
ret = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & BPF_TRAMP_F_CALL_ORIG) {
|
||||
if (fentry_cnt)
|
||||
if (fentry->nr_progs || fmod_ret->nr_progs)
|
||||
restore_regs(m, &prog, nr_args, stack_size);
|
||||
|
||||
/* call original function */
|
||||
if (emit_call(&prog, orig_call, prog))
|
||||
return -EINVAL;
|
||||
if (emit_call(&prog, orig_call, prog)) {
|
||||
ret = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
/* remember return value in a stack for bpf prog to access */
|
||||
emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -8);
|
||||
}
|
||||
|
||||
if (fexit_cnt)
|
||||
if (invoke_bpf(m, &prog, fexit_progs, fexit_cnt, stack_size))
|
||||
return -EINVAL;
|
||||
if (fmod_ret->nr_progs) {
|
||||
/* From Intel 64 and IA-32 Architectures Optimization
|
||||
* Reference Manual, 3.4.1.4 Code Alignment, Assembly/Compiler
|
||||
* Coding Rule 11: All branch targets should be 16-byte
|
||||
* aligned.
|
||||
*/
|
||||
emit_align(&prog, 16);
|
||||
/* Update the branches saved in invoke_bpf_mod_ret with the
|
||||
* aligned address of do_fexit.
|
||||
*/
|
||||
for (i = 0; i < fmod_ret->nr_progs; i++)
|
||||
emit_cond_near_jump(&branches[i], prog, branches[i],
|
||||
X86_JNE);
|
||||
}
|
||||
|
||||
if (fexit->nr_progs)
|
||||
if (invoke_bpf(m, &prog, fexit, stack_size)) {
|
||||
ret = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (flags & BPF_TRAMP_F_RESTORE_REGS)
|
||||
restore_regs(m, &prog, nr_args, stack_size);
|
||||
|
||||
/* This needs to be done regardless. If there were fmod_ret programs,
|
||||
* the return value is only updated on the stack and still needs to be
|
||||
* restored to R0.
|
||||
*/
|
||||
if (flags & BPF_TRAMP_F_CALL_ORIG)
|
||||
/* restore original return value back into RAX */
|
||||
emit_ldx(&prog, BPF_DW, BPF_REG_0, BPF_REG_FP, -8);
|
||||
@ -1525,45 +1688,15 @@ int arch_prepare_bpf_trampoline(void *image, void *image_end,
|
||||
EMIT4(0x48, 0x83, 0xC4, 8); /* add rsp, 8 */
|
||||
EMIT1(0xC3); /* ret */
|
||||
/* Make sure the trampoline generation logic doesn't overflow */
|
||||
if (WARN_ON_ONCE(prog > (u8 *)image_end - BPF_INSN_SAFETY))
|
||||
return -EFAULT;
|
||||
return prog - (u8 *)image;
|
||||
}
|
||||
|
||||
static int emit_cond_near_jump(u8 **pprog, void *func, void *ip, u8 jmp_cond)
|
||||
{
|
||||
u8 *prog = *pprog;
|
||||
int cnt = 0;
|
||||
s64 offset;
|
||||
|
||||
offset = func - (ip + 2 + 4);
|
||||
if (!is_simm32(offset)) {
|
||||
pr_err("Target %p is out of range\n", func);
|
||||
return -EINVAL;
|
||||
if (WARN_ON_ONCE(prog > (u8 *)image_end - BPF_INSN_SAFETY)) {
|
||||
ret = -EFAULT;
|
||||
goto cleanup;
|
||||
}
|
||||
EMIT2_off32(0x0F, jmp_cond + 0x10, offset);
|
||||
*pprog = prog;
|
||||
return 0;
|
||||
}
|
||||
ret = prog - (u8 *)image;
|
||||
|
||||
static void emit_nops(u8 **pprog, unsigned int len)
|
||||
{
|
||||
unsigned int i, noplen;
|
||||
u8 *prog = *pprog;
|
||||
int cnt = 0;
|
||||
|
||||
while (len > 0) {
|
||||
noplen = len;
|
||||
|
||||
if (noplen > ASM_NOP_MAX)
|
||||
noplen = ASM_NOP_MAX;
|
||||
|
||||
for (i = 0; i < noplen; i++)
|
||||
EMIT1(ideal_nops[noplen][i]);
|
||||
len -= noplen;
|
||||
}
|
||||
|
||||
*pprog = prog;
|
||||
cleanup:
|
||||
kfree(branches);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int emit_fallback_jump(u8 **pprog)
|
||||
@ -1588,7 +1721,7 @@ static int emit_fallback_jump(u8 **pprog)
|
||||
|
||||
static int emit_bpf_dispatcher(u8 **pprog, int a, int b, s64 *progs)
|
||||
{
|
||||
u8 *jg_reloc, *jg_target, *prog = *pprog;
|
||||
u8 *jg_reloc, *prog = *pprog;
|
||||
int pivot, err, jg_bytes = 1, cnt = 0;
|
||||
s64 jg_offset;
|
||||
|
||||
@ -1643,9 +1776,7 @@ static int emit_bpf_dispatcher(u8 **pprog, int a, int b, s64 *progs)
|
||||
* Coding Rule 11: All branch targets should be 16-byte
|
||||
* aligned.
|
||||
*/
|
||||
jg_target = PTR_ALIGN(prog, 16);
|
||||
if (jg_target != prog)
|
||||
emit_nops(&prog, jg_target - prog);
|
||||
emit_align(&prog, 16);
|
||||
jg_offset = prog - jg_reloc;
|
||||
emit_code(jg_reloc - jg_bytes, jg_offset, jg_bytes);
|
||||
|
||||
|
@ -433,6 +433,16 @@ struct btf_func_model {
|
||||
*/
|
||||
#define BPF_TRAMP_F_SKIP_FRAME BIT(2)
|
||||
|
||||
/* Each call __bpf_prog_enter + call bpf_func + call __bpf_prog_exit is ~50
|
||||
* bytes on x86. Pick a number to fit into BPF_IMAGE_SIZE / 2
|
||||
*/
|
||||
#define BPF_MAX_TRAMP_PROGS 40
|
||||
|
||||
struct bpf_tramp_progs {
|
||||
struct bpf_prog *progs[BPF_MAX_TRAMP_PROGS];
|
||||
int nr_progs;
|
||||
};
|
||||
|
||||
/* Different use cases for BPF trampoline:
|
||||
* 1. replace nop at the function entry (kprobe equivalent)
|
||||
* flags = BPF_TRAMP_F_RESTORE_REGS
|
||||
@ -455,8 +465,7 @@ struct btf_func_model {
|
||||
*/
|
||||
int arch_prepare_bpf_trampoline(void *image, void *image_end,
|
||||
const struct btf_func_model *m, u32 flags,
|
||||
struct bpf_prog **fentry_progs, int fentry_cnt,
|
||||
struct bpf_prog **fexit_progs, int fexit_cnt,
|
||||
struct bpf_tramp_progs *tprogs,
|
||||
void *orig_call);
|
||||
/* these two functions are called from generated trampoline */
|
||||
u64 notrace __bpf_prog_enter(void);
|
||||
@ -465,6 +474,7 @@ void notrace __bpf_prog_exit(struct bpf_prog *prog, u64 start);
|
||||
enum bpf_tramp_prog_type {
|
||||
BPF_TRAMP_FENTRY,
|
||||
BPF_TRAMP_FEXIT,
|
||||
BPF_TRAMP_MODIFY_RETURN,
|
||||
BPF_TRAMP_MAX,
|
||||
BPF_TRAMP_REPLACE, /* more than MAX */
|
||||
};
|
||||
@ -1146,6 +1156,9 @@ int bpf_prog_test_run_xdp(struct bpf_prog *prog, const union bpf_attr *kattr,
|
||||
union bpf_attr __user *uattr);
|
||||
int bpf_prog_test_run_skb(struct bpf_prog *prog, const union bpf_attr *kattr,
|
||||
union bpf_attr __user *uattr);
|
||||
int bpf_prog_test_run_tracing(struct bpf_prog *prog,
|
||||
const union bpf_attr *kattr,
|
||||
union bpf_attr __user *uattr);
|
||||
int bpf_prog_test_run_flow_dissector(struct bpf_prog *prog,
|
||||
const union bpf_attr *kattr,
|
||||
union bpf_attr __user *uattr);
|
||||
@ -1303,6 +1316,13 @@ static inline int bpf_prog_test_run_skb(struct bpf_prog *prog,
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static inline int bpf_prog_test_run_tracing(struct bpf_prog *prog,
|
||||
const union bpf_attr *kattr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static inline int bpf_prog_test_run_flow_dissector(struct bpf_prog *prog,
|
||||
const union bpf_attr *kattr,
|
||||
union bpf_attr __user *uattr)
|
||||
|
@ -210,6 +210,7 @@ enum bpf_attach_type {
|
||||
BPF_TRACE_RAW_TP,
|
||||
BPF_TRACE_FENTRY,
|
||||
BPF_TRACE_FEXIT,
|
||||
BPF_MODIFY_RETURN,
|
||||
__MAX_BPF_ATTACH_TYPE
|
||||
};
|
||||
|
||||
|
@ -320,6 +320,7 @@ static int bpf_struct_ops_map_update_elem(struct bpf_map *map, void *key,
|
||||
struct bpf_struct_ops_value *uvalue, *kvalue;
|
||||
const struct btf_member *member;
|
||||
const struct btf_type *t = st_ops->type;
|
||||
struct bpf_tramp_progs *tprogs = NULL;
|
||||
void *udata, *kdata;
|
||||
int prog_fd, err = 0;
|
||||
void *image;
|
||||
@ -343,6 +344,10 @@ static int bpf_struct_ops_map_update_elem(struct bpf_map *map, void *key,
|
||||
if (uvalue->state || refcount_read(&uvalue->refcnt))
|
||||
return -EINVAL;
|
||||
|
||||
tprogs = kcalloc(BPF_TRAMP_MAX, sizeof(*tprogs), GFP_KERNEL);
|
||||
if (!tprogs)
|
||||
return -ENOMEM;
|
||||
|
||||
uvalue = (struct bpf_struct_ops_value *)st_map->uvalue;
|
||||
kvalue = (struct bpf_struct_ops_value *)&st_map->kvalue;
|
||||
|
||||
@ -425,10 +430,12 @@ static int bpf_struct_ops_map_update_elem(struct bpf_map *map, void *key,
|
||||
goto reset_unlock;
|
||||
}
|
||||
|
||||
tprogs[BPF_TRAMP_FENTRY].progs[0] = prog;
|
||||
tprogs[BPF_TRAMP_FENTRY].nr_progs = 1;
|
||||
err = arch_prepare_bpf_trampoline(image,
|
||||
st_map->image + PAGE_SIZE,
|
||||
&st_ops->func_models[i], 0,
|
||||
&prog, 1, NULL, 0, NULL);
|
||||
tprogs, NULL);
|
||||
if (err < 0)
|
||||
goto reset_unlock;
|
||||
|
||||
@ -469,6 +476,7 @@ reset_unlock:
|
||||
memset(uvalue, 0, map->value_size);
|
||||
memset(kvalue, 0, map->value_size);
|
||||
unlock:
|
||||
kfree(tprogs);
|
||||
mutex_unlock(&st_map->lock);
|
||||
return err;
|
||||
}
|
||||
|
@ -3710,13 +3710,26 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
|
||||
nr_args--;
|
||||
}
|
||||
|
||||
if (prog->expected_attach_type == BPF_TRACE_FEXIT &&
|
||||
arg == nr_args) {
|
||||
if (!t)
|
||||
/* Default prog with 5 args. 6th arg is retval. */
|
||||
return true;
|
||||
/* function return type */
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
if (arg == nr_args) {
|
||||
if (prog->expected_attach_type == BPF_TRACE_FEXIT) {
|
||||
if (!t)
|
||||
return true;
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
} else if (prog->expected_attach_type == BPF_MODIFY_RETURN) {
|
||||
/* For now the BPF_MODIFY_RETURN can only be attached to
|
||||
* functions that return an int.
|
||||
*/
|
||||
if (!t)
|
||||
return false;
|
||||
|
||||
t = btf_type_skip_modifiers(btf, t->type, NULL);
|
||||
if (!btf_type_is_int(t)) {
|
||||
bpf_log(log,
|
||||
"ret type %s not allowed for fmod_ret\n",
|
||||
btf_kind_str[BTF_INFO_KIND(t->info)]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (arg >= nr_args) {
|
||||
bpf_log(log, "func '%s' doesn't have %d-th argument\n",
|
||||
tname, arg + 1);
|
||||
|
@ -2324,6 +2324,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog)
|
||||
|
||||
if (prog->expected_attach_type != BPF_TRACE_FENTRY &&
|
||||
prog->expected_attach_type != BPF_TRACE_FEXIT &&
|
||||
prog->expected_attach_type != BPF_MODIFY_RETURN &&
|
||||
prog->type != BPF_PROG_TYPE_EXT) {
|
||||
err = -EINVAL;
|
||||
goto out_put_prog;
|
||||
|
@ -190,40 +190,50 @@ static int register_fentry(struct bpf_trampoline *tr, void *new_addr)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Each call __bpf_prog_enter + call bpf_func + call __bpf_prog_exit is ~50
|
||||
* bytes on x86. Pick a number to fit into BPF_IMAGE_SIZE / 2
|
||||
*/
|
||||
#define BPF_MAX_TRAMP_PROGS 40
|
||||
static struct bpf_tramp_progs *
|
||||
bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total)
|
||||
{
|
||||
const struct bpf_prog_aux *aux;
|
||||
struct bpf_tramp_progs *tprogs;
|
||||
struct bpf_prog **progs;
|
||||
int kind;
|
||||
|
||||
*total = 0;
|
||||
tprogs = kcalloc(BPF_TRAMP_MAX, sizeof(*tprogs), GFP_KERNEL);
|
||||
if (!tprogs)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
for (kind = 0; kind < BPF_TRAMP_MAX; kind++) {
|
||||
tprogs[kind].nr_progs = tr->progs_cnt[kind];
|
||||
*total += tr->progs_cnt[kind];
|
||||
progs = tprogs[kind].progs;
|
||||
|
||||
hlist_for_each_entry(aux, &tr->progs_hlist[kind], tramp_hlist)
|
||||
*progs++ = aux->prog;
|
||||
}
|
||||
return tprogs;
|
||||
}
|
||||
|
||||
static int bpf_trampoline_update(struct bpf_trampoline *tr)
|
||||
{
|
||||
void *old_image = tr->image + ((tr->selector + 1) & 1) * BPF_IMAGE_SIZE/2;
|
||||
void *new_image = tr->image + (tr->selector & 1) * BPF_IMAGE_SIZE/2;
|
||||
struct bpf_prog *progs_to_run[BPF_MAX_TRAMP_PROGS];
|
||||
int fentry_cnt = tr->progs_cnt[BPF_TRAMP_FENTRY];
|
||||
int fexit_cnt = tr->progs_cnt[BPF_TRAMP_FEXIT];
|
||||
struct bpf_prog **progs, **fentry, **fexit;
|
||||
struct bpf_tramp_progs *tprogs;
|
||||
u32 flags = BPF_TRAMP_F_RESTORE_REGS;
|
||||
struct bpf_prog_aux *aux;
|
||||
int err;
|
||||
int err, total;
|
||||
|
||||
if (fentry_cnt + fexit_cnt == 0) {
|
||||
tprogs = bpf_trampoline_get_progs(tr, &total);
|
||||
if (IS_ERR(tprogs))
|
||||
return PTR_ERR(tprogs);
|
||||
|
||||
if (total == 0) {
|
||||
err = unregister_fentry(tr, old_image);
|
||||
tr->selector = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* populate fentry progs */
|
||||
fentry = progs = progs_to_run;
|
||||
hlist_for_each_entry(aux, &tr->progs_hlist[BPF_TRAMP_FENTRY], tramp_hlist)
|
||||
*progs++ = aux->prog;
|
||||
|
||||
/* populate fexit progs */
|
||||
fexit = progs;
|
||||
hlist_for_each_entry(aux, &tr->progs_hlist[BPF_TRAMP_FEXIT], tramp_hlist)
|
||||
*progs++ = aux->prog;
|
||||
|
||||
if (fexit_cnt)
|
||||
if (tprogs[BPF_TRAMP_FEXIT].nr_progs ||
|
||||
tprogs[BPF_TRAMP_MODIFY_RETURN].nr_progs)
|
||||
flags = BPF_TRAMP_F_CALL_ORIG | BPF_TRAMP_F_SKIP_FRAME;
|
||||
|
||||
/* Though the second half of trampoline page is unused a task could be
|
||||
@ -232,12 +242,11 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr)
|
||||
* preempted task. Hence wait for tasks to voluntarily schedule or go
|
||||
* to userspace.
|
||||
*/
|
||||
|
||||
synchronize_rcu_tasks();
|
||||
|
||||
err = arch_prepare_bpf_trampoline(new_image, new_image + BPF_IMAGE_SIZE / 2,
|
||||
&tr->func.model, flags,
|
||||
fentry, fentry_cnt,
|
||||
fexit, fexit_cnt,
|
||||
&tr->func.model, flags, tprogs,
|
||||
tr->func.addr);
|
||||
if (err < 0)
|
||||
goto out;
|
||||
@ -252,6 +261,7 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr)
|
||||
goto out;
|
||||
tr->selector++;
|
||||
out:
|
||||
kfree(tprogs);
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -260,6 +270,8 @@ static enum bpf_tramp_prog_type bpf_attach_type_to_tramp(enum bpf_attach_type t)
|
||||
switch (t) {
|
||||
case BPF_TRACE_FENTRY:
|
||||
return BPF_TRAMP_FENTRY;
|
||||
case BPF_MODIFY_RETURN:
|
||||
return BPF_TRAMP_MODIFY_RETURN;
|
||||
case BPF_TRACE_FEXIT:
|
||||
return BPF_TRAMP_FEXIT;
|
||||
default:
|
||||
@ -409,8 +421,7 @@ void notrace __bpf_prog_exit(struct bpf_prog *prog, u64 start)
|
||||
int __weak
|
||||
arch_prepare_bpf_trampoline(void *image, void *image_end,
|
||||
const struct btf_func_model *m, u32 flags,
|
||||
struct bpf_prog **fentry_progs, int fentry_cnt,
|
||||
struct bpf_prog **fexit_progs, int fexit_cnt,
|
||||
struct bpf_tramp_progs *tprogs,
|
||||
void *orig_call)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <linux/sort.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/error-injection.h>
|
||||
|
||||
#include "disasm.h"
|
||||
|
||||
@ -9800,6 +9801,33 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
|
||||
|
||||
return 0;
|
||||
}
|
||||
#define SECURITY_PREFIX "security_"
|
||||
|
||||
static int check_attach_modify_return(struct bpf_verifier_env *env)
|
||||
{
|
||||
struct bpf_prog *prog = env->prog;
|
||||
unsigned long addr = (unsigned long) prog->aux->trampoline->func.addr;
|
||||
|
||||
if (within_error_injection_list(addr))
|
||||
return 0;
|
||||
|
||||
/* This is expected to be cleaned up in the future with the KRSI effort
|
||||
* introducing the LSM_HOOK macro for cleaning up lsm_hooks.h.
|
||||
*/
|
||||
if (!strncmp(SECURITY_PREFIX, prog->aux->attach_func_name,
|
||||
sizeof(SECURITY_PREFIX) - 1)) {
|
||||
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
verbose(env, "fmod_ret attach_btf_id %u (%s) is not modifiable\n",
|
||||
prog->aux->attach_btf_id, prog->aux->attach_func_name);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int check_attach_btf_id(struct bpf_verifier_env *env)
|
||||
{
|
||||
@ -9950,6 +9978,7 @@ static int check_attach_btf_id(struct bpf_verifier_env *env)
|
||||
if (!prog_extension)
|
||||
return -EINVAL;
|
||||
/* fallthrough */
|
||||
case BPF_MODIFY_RETURN:
|
||||
case BPF_TRACE_FENTRY:
|
||||
case BPF_TRACE_FEXIT:
|
||||
if (!btf_type_is_func(t)) {
|
||||
@ -9999,6 +10028,9 @@ static int check_attach_btf_id(struct bpf_verifier_env *env)
|
||||
}
|
||||
tr->func.addr = (void *)addr;
|
||||
prog->aux->trampoline = tr;
|
||||
|
||||
if (prog->expected_attach_type == BPF_MODIFY_RETURN)
|
||||
ret = check_attach_modify_return(env);
|
||||
out:
|
||||
mutex_unlock(&tr->mutex);
|
||||
if (ret)
|
||||
|
@ -1266,6 +1266,7 @@ const struct bpf_verifier_ops tracing_verifier_ops = {
|
||||
};
|
||||
|
||||
const struct bpf_prog_ops tracing_prog_ops = {
|
||||
.test_run = bpf_prog_test_run_tracing,
|
||||
};
|
||||
|
||||
static bool raw_tp_writable_prog_is_valid_access(int off, int size,
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <net/bpf_sk_storage.h>
|
||||
#include <net/sock.h>
|
||||
#include <net/tcp.h>
|
||||
#include <linux/error-injection.h>
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include <trace/events/bpf_test_run.h>
|
||||
@ -143,6 +144,14 @@ int noinline bpf_fentry_test6(u64 a, void *b, short c, int d, void *e, u64 f)
|
||||
return a + (long)b + c + d + (long)e + f;
|
||||
}
|
||||
|
||||
int noinline bpf_modify_return_test(int a, int *b)
|
||||
{
|
||||
*b += 1;
|
||||
return a + *b;
|
||||
}
|
||||
|
||||
ALLOW_ERROR_INJECTION(bpf_modify_return_test, ERRNO);
|
||||
|
||||
static void *bpf_test_init(const union bpf_attr *kattr, u32 size,
|
||||
u32 headroom, u32 tailroom)
|
||||
{
|
||||
@ -160,18 +169,48 @@ static void *bpf_test_init(const union bpf_attr *kattr, u32 size,
|
||||
kfree(data);
|
||||
return ERR_PTR(-EFAULT);
|
||||
}
|
||||
if (bpf_fentry_test1(1) != 2 ||
|
||||
bpf_fentry_test2(2, 3) != 5 ||
|
||||
bpf_fentry_test3(4, 5, 6) != 15 ||
|
||||
bpf_fentry_test4((void *)7, 8, 9, 10) != 34 ||
|
||||
bpf_fentry_test5(11, (void *)12, 13, 14, 15) != 65 ||
|
||||
bpf_fentry_test6(16, (void *)17, 18, 19, (void *)20, 21) != 111) {
|
||||
kfree(data);
|
||||
return ERR_PTR(-EFAULT);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
int bpf_prog_test_run_tracing(struct bpf_prog *prog,
|
||||
const union bpf_attr *kattr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
u16 side_effect = 0, ret = 0;
|
||||
int b = 2, err = -EFAULT;
|
||||
u32 retval = 0;
|
||||
|
||||
switch (prog->expected_attach_type) {
|
||||
case BPF_TRACE_FENTRY:
|
||||
case BPF_TRACE_FEXIT:
|
||||
if (bpf_fentry_test1(1) != 2 ||
|
||||
bpf_fentry_test2(2, 3) != 5 ||
|
||||
bpf_fentry_test3(4, 5, 6) != 15 ||
|
||||
bpf_fentry_test4((void *)7, 8, 9, 10) != 34 ||
|
||||
bpf_fentry_test5(11, (void *)12, 13, 14, 15) != 65 ||
|
||||
bpf_fentry_test6(16, (void *)17, 18, 19, (void *)20, 21) != 111)
|
||||
goto out;
|
||||
break;
|
||||
case BPF_MODIFY_RETURN:
|
||||
ret = bpf_modify_return_test(1, &b);
|
||||
if (b != 2)
|
||||
side_effect = 1;
|
||||
break;
|
||||
default:
|
||||
goto out;
|
||||
}
|
||||
|
||||
retval = ((u32)side_effect << 16) | ret;
|
||||
if (copy_to_user(&uattr->test.retval, &retval, sizeof(retval)))
|
||||
goto out;
|
||||
|
||||
err = 0;
|
||||
out:
|
||||
trace_bpf_test_finish(&err);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void *bpf_ctx_init(const union bpf_attr *kattr, u32 max_size)
|
||||
{
|
||||
void __user *data_in = u64_to_user_ptr(kattr->test.ctx_in);
|
||||
|
@ -210,6 +210,7 @@ enum bpf_attach_type {
|
||||
BPF_TRACE_RAW_TP,
|
||||
BPF_TRACE_FENTRY,
|
||||
BPF_TRACE_FEXIT,
|
||||
BPF_MODIFY_RETURN,
|
||||
__MAX_BPF_ATTACH_TYPE
|
||||
};
|
||||
|
||||
|
@ -6288,6 +6288,10 @@ static const struct bpf_sec_def section_defs[] = {
|
||||
.expected_attach_type = BPF_TRACE_FENTRY,
|
||||
.is_attach_btf = true,
|
||||
.attach_fn = attach_trace),
|
||||
SEC_DEF("fmod_ret/", TRACING,
|
||||
.expected_attach_type = BPF_MODIFY_RETURN,
|
||||
.is_attach_btf = true,
|
||||
.attach_fn = attach_trace),
|
||||
SEC_DEF("fexit/", TRACING,
|
||||
.expected_attach_type = BPF_TRACE_FEXIT,
|
||||
.is_attach_btf = true,
|
||||
|
@ -1,22 +1,17 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2019 Facebook */
|
||||
#include <test_progs.h>
|
||||
#include "test_pkt_access.skel.h"
|
||||
#include "fentry_test.skel.h"
|
||||
#include "fexit_test.skel.h"
|
||||
|
||||
void test_fentry_fexit(void)
|
||||
{
|
||||
struct test_pkt_access *pkt_skel = NULL;
|
||||
struct fentry_test *fentry_skel = NULL;
|
||||
struct fexit_test *fexit_skel = NULL;
|
||||
__u64 *fentry_res, *fexit_res;
|
||||
__u32 duration = 0, retval;
|
||||
int err, pkt_fd, i;
|
||||
int err, prog_fd, i;
|
||||
|
||||
pkt_skel = test_pkt_access__open_and_load();
|
||||
if (CHECK(!pkt_skel, "pkt_skel_load", "pkt_access skeleton failed\n"))
|
||||
return;
|
||||
fentry_skel = fentry_test__open_and_load();
|
||||
if (CHECK(!fentry_skel, "fentry_skel_load", "fentry skeleton failed\n"))
|
||||
goto close_prog;
|
||||
@ -31,8 +26,8 @@ void test_fentry_fexit(void)
|
||||
if (CHECK(err, "fexit_attach", "fexit attach failed: %d\n", err))
|
||||
goto close_prog;
|
||||
|
||||
pkt_fd = bpf_program__fd(pkt_skel->progs.test_pkt_access);
|
||||
err = bpf_prog_test_run(pkt_fd, 1, &pkt_v6, sizeof(pkt_v6),
|
||||
prog_fd = bpf_program__fd(fexit_skel->progs.test1);
|
||||
err = bpf_prog_test_run(prog_fd, 1, NULL, 0,
|
||||
NULL, NULL, &retval, &duration);
|
||||
CHECK(err || retval, "ipv6",
|
||||
"err %d errno %d retval %d duration %d\n",
|
||||
@ -49,7 +44,6 @@ void test_fentry_fexit(void)
|
||||
}
|
||||
|
||||
close_prog:
|
||||
test_pkt_access__destroy(pkt_skel);
|
||||
fentry_test__destroy(fentry_skel);
|
||||
fexit_test__destroy(fexit_skel);
|
||||
}
|
||||
|
@ -1,20 +1,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2019 Facebook */
|
||||
#include <test_progs.h>
|
||||
#include "test_pkt_access.skel.h"
|
||||
#include "fentry_test.skel.h"
|
||||
|
||||
void test_fentry_test(void)
|
||||
{
|
||||
struct test_pkt_access *pkt_skel = NULL;
|
||||
struct fentry_test *fentry_skel = NULL;
|
||||
int err, pkt_fd, i;
|
||||
int err, prog_fd, i;
|
||||
__u32 duration = 0, retval;
|
||||
__u64 *result;
|
||||
|
||||
pkt_skel = test_pkt_access__open_and_load();
|
||||
if (CHECK(!pkt_skel, "pkt_skel_load", "pkt_access skeleton failed\n"))
|
||||
return;
|
||||
fentry_skel = fentry_test__open_and_load();
|
||||
if (CHECK(!fentry_skel, "fentry_skel_load", "fentry skeleton failed\n"))
|
||||
goto cleanup;
|
||||
@ -23,10 +18,10 @@ void test_fentry_test(void)
|
||||
if (CHECK(err, "fentry_attach", "fentry attach failed: %d\n", err))
|
||||
goto cleanup;
|
||||
|
||||
pkt_fd = bpf_program__fd(pkt_skel->progs.test_pkt_access);
|
||||
err = bpf_prog_test_run(pkt_fd, 1, &pkt_v6, sizeof(pkt_v6),
|
||||
prog_fd = bpf_program__fd(fentry_skel->progs.test1);
|
||||
err = bpf_prog_test_run(prog_fd, 1, NULL, 0,
|
||||
NULL, NULL, &retval, &duration);
|
||||
CHECK(err || retval, "ipv6",
|
||||
CHECK(err || retval, "test_run",
|
||||
"err %d errno %d retval %d duration %d\n",
|
||||
err, errno, retval, duration);
|
||||
|
||||
@ -39,5 +34,4 @@ void test_fentry_test(void)
|
||||
|
||||
cleanup:
|
||||
fentry_test__destroy(fentry_skel);
|
||||
test_pkt_access__destroy(pkt_skel);
|
||||
}
|
||||
|
@ -1,64 +1,37 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2019 Facebook */
|
||||
#include <test_progs.h>
|
||||
#include "fexit_test.skel.h"
|
||||
|
||||
void test_fexit_test(void)
|
||||
{
|
||||
struct bpf_prog_load_attr attr = {
|
||||
.file = "./fexit_test.o",
|
||||
};
|
||||
|
||||
char prog_name[] = "fexit/bpf_fentry_testX";
|
||||
struct bpf_object *obj = NULL, *pkt_obj;
|
||||
int err, pkt_fd, kfree_skb_fd, i;
|
||||
struct bpf_link *link[6] = {};
|
||||
struct bpf_program *prog[6];
|
||||
struct fexit_test *fexit_skel = NULL;
|
||||
int err, prog_fd, i;
|
||||
__u32 duration = 0, retval;
|
||||
struct bpf_map *data_map;
|
||||
const int zero = 0;
|
||||
u64 result[6];
|
||||
__u64 *result;
|
||||
|
||||
err = bpf_prog_load("./test_pkt_access.o", BPF_PROG_TYPE_SCHED_CLS,
|
||||
&pkt_obj, &pkt_fd);
|
||||
if (CHECK(err, "prog_load sched cls", "err %d errno %d\n", err, errno))
|
||||
return;
|
||||
err = bpf_prog_load_xattr(&attr, &obj, &kfree_skb_fd);
|
||||
if (CHECK(err, "prog_load fail", "err %d errno %d\n", err, errno))
|
||||
goto close_prog;
|
||||
fexit_skel = fexit_test__open_and_load();
|
||||
if (CHECK(!fexit_skel, "fexit_skel_load", "fexit skeleton failed\n"))
|
||||
goto cleanup;
|
||||
|
||||
for (i = 0; i < 6; i++) {
|
||||
prog_name[sizeof(prog_name) - 2] = '1' + i;
|
||||
prog[i] = bpf_object__find_program_by_title(obj, prog_name);
|
||||
if (CHECK(!prog[i], "find_prog", "prog %s not found\n", prog_name))
|
||||
goto close_prog;
|
||||
link[i] = bpf_program__attach_trace(prog[i]);
|
||||
if (CHECK(IS_ERR(link[i]), "attach_trace", "failed to link\n"))
|
||||
goto close_prog;
|
||||
}
|
||||
data_map = bpf_object__find_map_by_name(obj, "fexit_te.bss");
|
||||
if (CHECK(!data_map, "find_data_map", "data map not found\n"))
|
||||
goto close_prog;
|
||||
err = fexit_test__attach(fexit_skel);
|
||||
if (CHECK(err, "fexit_attach", "fexit attach failed: %d\n", err))
|
||||
goto cleanup;
|
||||
|
||||
err = bpf_prog_test_run(pkt_fd, 1, &pkt_v6, sizeof(pkt_v6),
|
||||
prog_fd = bpf_program__fd(fexit_skel->progs.test1);
|
||||
err = bpf_prog_test_run(prog_fd, 1, NULL, 0,
|
||||
NULL, NULL, &retval, &duration);
|
||||
CHECK(err || retval, "ipv6",
|
||||
CHECK(err || retval, "test_run",
|
||||
"err %d errno %d retval %d duration %d\n",
|
||||
err, errno, retval, duration);
|
||||
|
||||
err = bpf_map_lookup_elem(bpf_map__fd(data_map), &zero, &result);
|
||||
if (CHECK(err, "get_result",
|
||||
"failed to get output data: %d\n", err))
|
||||
goto close_prog;
|
||||
result = (__u64 *)fexit_skel->bss;
|
||||
for (i = 0; i < 6; i++) {
|
||||
if (CHECK(result[i] != 1, "result",
|
||||
"fexit_test%d failed err %lld\n", i + 1, result[i]))
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
for (i = 0; i < 6; i++)
|
||||
if (CHECK(result[i] != 1, "result", "bpf_fentry_test%d failed err %ld\n",
|
||||
i + 1, result[i]))
|
||||
goto close_prog;
|
||||
|
||||
close_prog:
|
||||
for (i = 0; i < 6; i++)
|
||||
if (!IS_ERR_OR_NULL(link[i]))
|
||||
bpf_link__destroy(link[i]);
|
||||
bpf_object__close(obj);
|
||||
bpf_object__close(pkt_obj);
|
||||
cleanup:
|
||||
fexit_test__destroy(fexit_skel);
|
||||
}
|
||||
|
65
tools/testing/selftests/bpf/prog_tests/modify_return.c
Normal file
65
tools/testing/selftests/bpf/prog_tests/modify_return.c
Normal file
@ -0,0 +1,65 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* Copyright 2020 Google LLC.
|
||||
*/
|
||||
|
||||
#include <test_progs.h>
|
||||
#include "modify_return.skel.h"
|
||||
|
||||
#define LOWER(x) ((x) & 0xffff)
|
||||
#define UPPER(x) ((x) >> 16)
|
||||
|
||||
|
||||
static void run_test(__u32 input_retval, __u16 want_side_effect, __s16 want_ret)
|
||||
{
|
||||
struct modify_return *skel = NULL;
|
||||
int err, prog_fd;
|
||||
__u32 duration = 0, retval;
|
||||
__u16 side_effect;
|
||||
__s16 ret;
|
||||
|
||||
skel = modify_return__open_and_load();
|
||||
if (CHECK(!skel, "skel_load", "modify_return skeleton failed\n"))
|
||||
goto cleanup;
|
||||
|
||||
err = modify_return__attach(skel);
|
||||
if (CHECK(err, "modify_return", "attach failed: %d\n", err))
|
||||
goto cleanup;
|
||||
|
||||
skel->bss->input_retval = input_retval;
|
||||
prog_fd = bpf_program__fd(skel->progs.fmod_ret_test);
|
||||
err = bpf_prog_test_run(prog_fd, 1, NULL, 0, NULL, 0,
|
||||
&retval, &duration);
|
||||
|
||||
CHECK(err, "test_run", "err %d errno %d\n", err, errno);
|
||||
|
||||
side_effect = UPPER(retval);
|
||||
ret = LOWER(retval);
|
||||
|
||||
CHECK(ret != want_ret, "test_run",
|
||||
"unexpected ret: %d, expected: %d\n", ret, want_ret);
|
||||
CHECK(side_effect != want_side_effect, "modify_return",
|
||||
"unexpected side_effect: %d\n", side_effect);
|
||||
|
||||
CHECK(skel->bss->fentry_result != 1, "modify_return",
|
||||
"fentry failed\n");
|
||||
CHECK(skel->bss->fexit_result != 1, "modify_return",
|
||||
"fexit failed\n");
|
||||
CHECK(skel->bss->fmod_ret_result != 1, "modify_return",
|
||||
"fmod_ret failed\n");
|
||||
|
||||
cleanup:
|
||||
modify_return__destroy(skel);
|
||||
}
|
||||
|
||||
void test_modify_return(void)
|
||||
{
|
||||
run_test(0 /* input_retval */,
|
||||
1 /* want_side_effect */,
|
||||
4 /* want_ret */);
|
||||
run_test(-EINVAL /* input_retval */,
|
||||
0 /* want_side_effect */,
|
||||
-EINVAL /* want_ret */);
|
||||
}
|
||||
|
49
tools/testing/selftests/bpf/progs/modify_return.c
Normal file
49
tools/testing/selftests/bpf/progs/modify_return.c
Normal file
@ -0,0 +1,49 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* Copyright 2020 Google LLC.
|
||||
*/
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
static int sequence = 0;
|
||||
__s32 input_retval = 0;
|
||||
|
||||
__u64 fentry_result = 0;
|
||||
SEC("fentry/bpf_modify_return_test")
|
||||
int BPF_PROG(fentry_test, int a, __u64 b)
|
||||
{
|
||||
sequence++;
|
||||
fentry_result = (sequence == 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
__u64 fmod_ret_result = 0;
|
||||
SEC("fmod_ret/bpf_modify_return_test")
|
||||
int BPF_PROG(fmod_ret_test, int a, int *b, int ret)
|
||||
{
|
||||
sequence++;
|
||||
/* This is the first fmod_ret program, the ret passed should be 0 */
|
||||
fmod_ret_result = (sequence == 2 && ret == 0);
|
||||
return input_retval;
|
||||
}
|
||||
|
||||
__u64 fexit_result = 0;
|
||||
SEC("fexit/bpf_modify_return_test")
|
||||
int BPF_PROG(fexit_test, int a, __u64 b, int ret)
|
||||
{
|
||||
sequence++;
|
||||
/* If the input_reval is non-zero a successful modification should have
|
||||
* occurred.
|
||||
*/
|
||||
if (input_retval)
|
||||
fexit_result = (sequence == 3 && ret == input_retval);
|
||||
else
|
||||
fexit_result = (sequence == 3 && ret == 4);
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user