mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
bpf: Support passing context output arguments to kfuncs
Allows programs to pass a context argument that points to a scalar output value on to a kfunc that writes the result on the program's behalf. A ctx_arg_info entry can now describe a fixed-size PTR_TO_MEM context argument through a new mem_size field, honored in btf_ctx_access(). Loading the argument yields a PTR_TO_MEM | MEM_RDONLY | PTR_TRUSTED register of known size, readable but not writable by the program. A kfunc argument tagged with the new "__ctx_out" suffix accepts only that register type with a size matching the pointed-to type, so the only value a program can pass is an output argument from its own context. The pointer stays read-only to the program because the value is trusted by whoever invoked the BPF program. In the first use case, the inode_init_security LSM hook, every LSM receives a shared xattr array and an int *xattr_count that lsm_get_xattr_slot() increments; a program that could store a garbage count would push another LSM's write out of bounds, so only the kfunc itself writes through it. Suggested-by: Kumar Kartikeya Dwivedi <memxor@gmail.com> Co-developed-by: Daniel Borkmann <daniel@iogearbox.net> Signed-off-by: David Windsor <dwindsor@gmail.com> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://patch.msgid.link/20260915150739.284189-4-daniel@iogearbox.net
This commit is contained in:
parent
c42da1d3ba
commit
806fc431e0
|
|
@ -315,6 +315,29 @@ However, there is no obligation to prove to the verifier that such a pointer is
|
|||
non-NULL before use, in-line with existing semantics of arena pointers used in
|
||||
a program (or obtained from any other source).
|
||||
|
||||
2.3.9 __ctx_out Annotation
|
||||
--------------------------
|
||||
|
||||
This annotation is used to indicate that the argument is an output
|
||||
parameter of the attached hook, passed through from the program's
|
||||
context. The verifier requires the register to be a trusted read-only
|
||||
pointer to fixed-size memory, which can only be produced by loading an
|
||||
argument described by the program's ctx_arg_info from the context. The
|
||||
program itself cannot write through the pointer; the kfunc may.
|
||||
|
||||
An example is given below::
|
||||
|
||||
__bpf_kfunc int bpf_inode_init_xattr(struct xattr *xattrs,
|
||||
int *xattr_count__ctx_out,
|
||||
...)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
In this case, a program attached to the ``inode_init_security`` LSM hook
|
||||
can pass the hook's own ``xattr_count`` argument through to the kfunc,
|
||||
which claims xattr slots by writing through it.
|
||||
|
||||
.. _BPF_kfunc_nodef:
|
||||
|
||||
2.4 Using an existing kernel function
|
||||
|
|
|
|||
|
|
@ -924,6 +924,7 @@ enum bpf_arg_type {
|
|||
ARG_PTR_TO_TASK_WORK, /* pointer to bpf_task_work */
|
||||
ARG_PTR_TO_IRQ_FLAG, /* pointer to saved IRQ flags on the stack */
|
||||
ARG_PTR_TO_RES_SPIN_LOCK, /* pointer to bpf_res_spin_lock */
|
||||
ARG_PTR_TO_CTX_OUT, /* hook output argument passed through from ctx */
|
||||
ARG_PTR_TO_PROG_AUX, /* pointer to the caller's bpf_prog_aux */
|
||||
ARG_IGNORE, /* argument the verifier does not check at all */
|
||||
__BPF_ARG_TYPE_MAX,
|
||||
|
|
@ -1138,6 +1139,7 @@ struct bpf_insn_access_aux {
|
|||
u32 ref_id;
|
||||
};
|
||||
};
|
||||
u32 mem_size;
|
||||
struct bpf_verifier_log *log; /* for verbose logs */
|
||||
bool is_retval; /* is accessing function return value ? */
|
||||
};
|
||||
|
|
@ -1716,6 +1718,7 @@ struct bpf_ctx_arg_aux {
|
|||
struct btf *btf;
|
||||
u32 btf_id;
|
||||
u32 ref_id;
|
||||
u32 mem_size;
|
||||
bool refcounted;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6995,8 +6995,9 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
|
|||
}
|
||||
|
||||
/*
|
||||
* Check for PTR_TO_RDONLY_BUF_OR_NULL, PTR_TO_RDWR_BUF_OR_NULL or
|
||||
* PTR_TO_ARENA (both nullable and non-nullable cases).
|
||||
* Check for PTR_TO_RDONLY_BUF_OR_NULL, PTR_TO_RDWR_BUF_OR_NULL,
|
||||
* PTR_TO_ARENA (both nullable and non-nullable cases) or fixed-size
|
||||
* PTR_TO_MEM.
|
||||
*/
|
||||
for (i = 0; i < prog->aux->ctx_arg_info_size; i++) {
|
||||
const struct bpf_ctx_arg_aux *ctx_arg_info = &prog->aux->ctx_arg_info[i];
|
||||
|
|
@ -7006,8 +7007,10 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
|
|||
flag = type_flag(ctx_arg_info->reg_type);
|
||||
if (ctx_arg_info->offset == off &&
|
||||
(type == PTR_TO_ARENA ||
|
||||
type == PTR_TO_MEM ||
|
||||
(type == PTR_TO_BUF && (flag & PTR_MAYBE_NULL)))) {
|
||||
info->reg_type = ctx_arg_info->reg_type;
|
||||
info->mem_size = ctx_arg_info->mem_size;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -984,6 +984,8 @@ const char *bpf_diag_arg_type_plain(enum bpf_arg_type type)
|
|||
return "the address of a stack iterator object for iterator new, next, and destroy calls";
|
||||
case ARG_PTR_TO_IRQ_FLAG:
|
||||
return "the same stack slot used by bpf_local_irq_save() or bpf_res_spin_lock_irqsave()";
|
||||
case ARG_PTR_TO_CTX_OUT:
|
||||
return "the attach hook's own output argument, loaded directly from the program context";
|
||||
default:
|
||||
return "a value with one of the accepted pointer or scalar types for this call";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6584,6 +6584,8 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
|
|||
regs[value_regno].btf = info.btf;
|
||||
regs[value_regno].btf_id = info.btf_id;
|
||||
regs[value_regno].id = info.ref_id;
|
||||
} else if (base_type(info.reg_type) == PTR_TO_MEM) {
|
||||
regs[value_regno].mem_size = info.mem_size;
|
||||
}
|
||||
if (type_may_be_null(info.reg_type) && !regs[value_regno].id)
|
||||
regs[value_regno].id = ++env->id_gen;
|
||||
|
|
@ -8363,6 +8365,9 @@ static const struct bpf_reg_types arena_types = {
|
|||
SCALAR_VALUE,
|
||||
}
|
||||
};
|
||||
static const struct bpf_reg_types ctx_out_types = {
|
||||
.types = { PTR_TO_MEM | MEM_RDONLY | PTR_TRUSTED },
|
||||
};
|
||||
|
||||
static const struct bpf_reg_types alloc_obj_drop_types = {
|
||||
.types = {
|
||||
|
|
@ -8434,6 +8439,7 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
|
|||
[ARG_PTR_TO_TASK_WORK] = &map_value_types,
|
||||
[ARG_PTR_TO_IRQ_FLAG] = &stack_ptr_types,
|
||||
[ARG_PTR_TO_ARENA] = &arena_types,
|
||||
[ARG_PTR_TO_CTX_OUT] = &ctx_out_types,
|
||||
};
|
||||
|
||||
static void bpf_diag_call_arg(struct bpf_verifier_env *env, u32 insn_idx, argno_t argno,
|
||||
|
|
@ -9426,6 +9432,13 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
|
|||
if (err < 0)
|
||||
return err;
|
||||
break;
|
||||
case ARG_PTR_TO_CTX_OUT:
|
||||
if (reg->mem_size != arg_size) {
|
||||
verbose(env, "%s expected %u bytes of ctx-provided memory, got %u\n",
|
||||
reg_arg_name(env, argno), arg_size, reg->mem_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
case ARG_PTR_TO_RES_SPIN_LOCK:
|
||||
{
|
||||
int flags;
|
||||
|
|
@ -12142,6 +12155,11 @@ static bool is_kfunc_arg_irq_flag(const struct btf *btf, const struct btf_param
|
|||
return btf_param_match_suffix(btf, arg, "__irq_flag");
|
||||
}
|
||||
|
||||
static bool is_kfunc_arg_ctx_out(const struct btf *btf, const struct btf_param *arg)
|
||||
{
|
||||
return btf_param_match_suffix(btf, arg, "__ctx_out");
|
||||
}
|
||||
|
||||
static bool is_kfunc_arg_arena(const struct btf *btf, const struct btf_param *arg)
|
||||
{
|
||||
return btf_param_match_suffix(btf, arg, "__arena__nullable") ||
|
||||
|
|
@ -12905,7 +12923,23 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
|
|||
arg_type = ARG_PTR_TO_IRQ_FLAG;
|
||||
else if (is_kfunc_arg_res_spin_lock(meta->btf, &args[arg]))
|
||||
arg_type = ARG_PTR_TO_RES_SPIN_LOCK;
|
||||
else if (is_kfunc_arg_callback(env, meta->btf, &args[arg]))
|
||||
else if (is_kfunc_arg_ctx_out(meta->btf, &args[arg])) {
|
||||
if (!btf_type_is_scalar(ref_t)) {
|
||||
verbose(env, "%s __ctx_out argument must point to a scalar\n",
|
||||
reg_arg_name(env, argno));
|
||||
return -EINVAL;
|
||||
}
|
||||
resolve_ret = btf_resolve_size(meta->btf, ref_t, &type_size);
|
||||
if (IS_ERR(resolve_ret)) {
|
||||
verbose(env,
|
||||
"%s reference type('%s %s') size cannot be determined: %ld\n",
|
||||
reg_arg_name(env, argno), btf_type_str(ref_t),
|
||||
ref_tname, PTR_ERR(resolve_ret));
|
||||
return -EINVAL;
|
||||
}
|
||||
proto->arg_size[arg] = type_size;
|
||||
arg_type = ARG_PTR_TO_CTX_OUT | MEM_FIXED_SIZE;
|
||||
} else if (is_kfunc_arg_callback(env, meta->btf, &args[arg]))
|
||||
arg_type = ARG_PTR_TO_FUNC;
|
||||
else if (is_kfunc_arg_arena(meta->btf, &args[arg])) {
|
||||
if (!bpf_jit_supports_arena_args()) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user