From af7d5bebc78d0db760f9d271f0a89621548bc015 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 26 Nov 2025 16:50:09 -0800 Subject: [PATCH] bpf: Allow const char * from LSM hooks as kfunc const string arguments Let the BPF verifier to recognize const char * arguments from LSM hooks (and other BPF program types) as valid const string pointers that can be passed to kfuncs expecting KF_ARG_PTR_TO_CONST_STR. Previously, kfuncs with KF_ARG_PTR_TO_CONST_STR only accepted PTR_TO_MAP_VALUE from readonly maps. This was limiting for LSM programs that receive const char * arguments from hooks like sb_mount's dev_name. Signed-off-by: Song Liu --- include/linux/btf.h | 1 + kernel/bpf/btf.c | 33 ++++++++++++++++++++++++++++ kernel/bpf/verifier.c | 51 +++++++++++++++++++++++++++++++++---------- 3 files changed, 73 insertions(+), 12 deletions(-) diff --git a/include/linux/btf.h b/include/linux/btf.h index f06976ffb63f9..bd5a32d332542 100644 --- a/include/linux/btf.h +++ b/include/linux/btf.h @@ -224,6 +224,7 @@ struct btf *btf_base_btf(const struct btf *btf); bool btf_type_is_i32(const struct btf_type *t); bool btf_type_is_i64(const struct btf_type *t); bool btf_type_is_primitive(const struct btf_type *t); +bool btf_type_is_const_char_ptr(const struct btf *btf, const struct btf_type *t); bool btf_member_is_reg_int(const struct btf *btf, const struct btf_type *s, const struct btf_member *m, u32 expected_offset, u32 expected_size); diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 0de8fc8a0e0b3..94a272585b973 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -897,6 +897,25 @@ bool btf_type_is_primitive(const struct btf_type *t) btf_is_any_enum(t); } +bool btf_type_is_const_char_ptr(const struct btf *btf, const struct btf_type *t) +{ + const char *tname; + + /* The type chain has to be PTR->CONST->CHAR */ + if (BTF_INFO_KIND(t->info) != BTF_KIND_PTR) + return false; + + t = btf_type_by_id(btf, t->type); + if (BTF_INFO_KIND(t->info) != BTF_KIND_CONST) + return false; + + t = btf_type_by_id(btf, t->type); + tname = btf_name_by_offset(btf, t->name_off); + if (tname && strcmp(tname, "char") == 0) + return true; + return false; +} + /* * Check that given struct member is a regular int with expected * offset and size. @@ -6746,6 +6765,20 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type, /* Default prog with MAX_BPF_FUNC_REG_ARGS args */ return true; t = btf_type_by_id(btf, args[arg].type); + + /* + * For const string, we need to match "const char *" + * exactly. Therefore, do the check before the skipping + * modifiers. + */ + if (btf_type_is_const_char_ptr(btf, t)) { + info->reg_type = PTR_TO_BTF_ID; + if (prog_args_trusted(prog)) + info->reg_type |= PTR_TRUSTED; + info->btf = btf; + info->btf_id = args[arg].type; + return true; + } } /* skip modifiers */ diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 766695491bc5b..a9757c056d4b7 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -9598,8 +9598,12 @@ static enum bpf_dynptr_type dynptr_get_type(struct bpf_verifier_env *env, return state->stack[spi].spilled_ptr.dynptr.type; } -static int check_reg_const_str(struct bpf_verifier_env *env, - struct bpf_reg_state *reg, u32 regno) +/* + * Check for const string saved in a bpf map. The caller is responsible + * to check reg->type == PTR_TO_MAP_VALUE. + */ +static int check_reg_const_str_in_map(struct bpf_verifier_env *env, + struct bpf_reg_state *reg, u32 regno) { struct bpf_map *map = reg->map_ptr; int err; @@ -9607,9 +9611,6 @@ static int check_reg_const_str(struct bpf_verifier_env *env, u64 map_addr; char *str_ptr; - if (reg->type != PTR_TO_MAP_VALUE) - return -EINVAL; - if (!bpf_map_is_rdonly(map)) { verbose(env, "R%d does not point to a readonly map'\n", regno); return -EACCES; @@ -9646,6 +9647,26 @@ static int check_reg_const_str(struct bpf_verifier_env *env, return 0; } +/* Check for const string passed in as input to the bpf program. */ +static int check_reg_const_str_arg(struct bpf_reg_state *reg) +{ + const struct btf *btf; + const struct btf_type *t; + const char *tname; + + if (base_type(reg->type) != PTR_TO_BTF_ID) + return -EINVAL; + + btf = reg->btf; + t = btf_type_by_id(btf, reg->btf_id); + if (!t) + return -EINVAL; + + if (btf_type_is_const_char_ptr(btf, t)) + return 0; + return -EINVAL; +} + /* Returns constant key value in `value` if possible, else negative error */ static int get_constant_map_key(struct bpf_verifier_env *env, struct bpf_reg_state *key, @@ -9964,7 +9985,9 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, break; case ARG_PTR_TO_CONST_STR: { - err = check_reg_const_str(env, reg, regno); + if (reg->type != PTR_TO_MAP_VALUE) + return -EINVAL; + err = check_reg_const_str_in_map(env, reg, regno); if (err) return err; break; @@ -13626,13 +13649,17 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ meta->arg_btf_id = reg->btf_id; break; case KF_ARG_PTR_TO_CONST_STR: - if (reg->type != PTR_TO_MAP_VALUE) { - verbose(env, "arg#%d doesn't point to a const string\n", i); - return -EINVAL; + if (reg->type == PTR_TO_MAP_VALUE) { + ret = check_reg_const_str_in_map(env, reg, regno); + if (ret) + return ret; + } else { + ret = check_reg_const_str_arg(reg); + if (ret) { + verbose(env, "arg#%d doesn't point to a const string\n", i); + return ret; + } } - ret = check_reg_const_str(env, reg, regno); - if (ret) - return ret; break; case KF_ARG_PTR_TO_WORKQUEUE: if (reg->type != PTR_TO_MAP_VALUE) {