Merge branch 'Support PTR_MAYBE_NULL for struct_ops arguments.'

Kui-Feng Lee says:

====================
Allow passing null pointers to the operators provided by a struct_ops
object. This is an RFC to collect feedbacks/opinions.

The function pointers that are passed to struct_ops operators (the function
pointers) are always considered reliable until now. They cannot be
null. However, in certain scenarios, it should be possible to pass null
pointers to these operators. For instance, sched_ext may pass a null
pointer in the struct task type to an operator that is provided by its
struct_ops objects.

The proposed solution here is to add PTR_MAYBE_NULL annotations to
arguments and create instances of struct bpf_ctx_arg_aux (arg_info) for
these arguments. These arg_infos will be installed at
prog->aux->ctx_arg_info and will be checked by the BPF verifier when
loading the programs. When a struct_ops program accesses arguments in the
ctx, the verifier will call btf_ctx_access() (through
bpf_verifier_ops->is_valid_access) to verify the access. btf_ctx_access()
will check arg_info and use the information of the matched arg_info to
properly set reg_type.

For nullable arguments, this patch sets an arg_info to label them with
PTR_TO_BTF_ID | PTR_TRUSTED | PTR_MAYBE_NULL. This enforces the verifier to
check programs and ensure that they properly check the pointer. The
programs should check if the pointer is null before reading/writing the
pointed memory.

The implementer of a struct_ops should annotate the arguments that can
be null. The implementer should define a stub function (empty) as a
placeholder for each defined operator. The name of a stub function
should be in the pattern "<st_op_type>__<operator name>". For example,
for test_maybe_null of struct bpf_testmod_ops, it's stub function name
should be "bpf_testmod_ops__test_maybe_null". You mark an argument
nullable by suffixing the argument name with "__nullable" at the stub
function.  Here is the example in bpf_testmod.c.

  static int bpf_testmod_ops__test_maybe_null(int dummy,
                                              struct task_struct *task__nullable)
  {
          return 0;
  }

This means that the argument 1 (2nd) of bpf_testmod_ops->test_maybe_null,
which is a function pointer that can be null. With this annotation, the
verifier will understand how to check programs using this arguments.  A BPF
program that implement test_maybe_null should check the pointer to make
sure it is not null before using it. For example,

  if (task__nullable)
      save_tgid = task__nullable->tgid

Without the check, the verifier will reject the program.

Since we already has stub functions for kCFI, we just reuse these stub
functions with the naming convention mentioned earlier. These stub
functions with the naming convention is only required if there are nullable
arguments to annotate. For functions without nullable arguments, stub
functions are not necessary for the purpose of this patch.
---
Major changes from v7:

 - Update a comment that is out of date.

Major changes from v6:

 - Remove "len" from bpf_struct_ops_desc_release().

 - Rename arg_info(s) to info, and rename all_arg_info to arg_info in
   prepare_arg_info().

 - Rename arg_info to info in struct bpf_struct_ops_arg_info.

Major changes from v5:

 - Rename all member_arg_info variables.

 - Refactor to bpf_struct_ops_desc_release() to share code
   between btf_free_struct_ops_tab() and bpf_struct_ops_desc_init().

 - Refactor to btf_param_match_suffix(). (Add a new patch as the part 2.)

 - Clean up the commit log and remaining code in the patch of test cases.

 - Update a comment in struct_ops_maybe_null.c.

Major changes from v4:

 - Remove the support of pointers to types other than struct
   types. That would be a separate patchset.

   - Remove the patch about extending PTR_TO_BTF_ID.

   - Remove the test against various pointer types from selftests.

 - Remove the patch "bpf: Remove an unnecessary check" and send that
   patch separately.

 - Remove member_arg_info_cnt from struct bpf_struct_ops_desc.

 - Use btf_id from FUNC_PROTO of a function pointer instead of a stub
   function.

Major changes from v3:

 - Move the code collecting argument information to prepare_arg_info()
   called in the loop in bpf_struct_ops_desc_init().

 - Simplify the memory allocation by having separated arg_info for
   each member of a struct_ops type.

 - Extend PTR_TO_BTF_ID to pointers to scalar types and array types,
   not only to struct types.

Major changes from v2:

 - Remove dead code.

 - Add comments to explain the code itself.

Major changes from v1:

 - Annotate arguments by suffixing argument names with "__nullable" at
   stub functions.

v7: https://lore.kernel.org/all/20240209020053.1132710-1-thinker.li@gmail.com/
v6: https://lore.kernel.org/all/20240208065103.2154768-1-thinker.li@gmail.com/
v5: https://lore.kernel.org/all/20240206063833.2520479-1-thinker.li@gmail.com/
v4: https://lore.kernel.org/all/20240202220516.1165466-1-thinker.li@gmail.com/
v3: https://lore.kernel.org/all/20240122212217.1391878-1-thinker.li@gmail.com/
v2: https://lore.kernel.org/all/20240118224922.336006-1-thinker.li@gmail.com/
====================

Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
This commit is contained in:
Martin KaFai Lau 2024-02-13 13:28:12 -08:00
commit 2c21a0f67c
10 changed files with 406 additions and 42 deletions

View File

@ -1415,6 +1415,7 @@ struct bpf_jit_poke_descriptor {
struct bpf_ctx_arg_aux {
u32 offset;
enum bpf_reg_type reg_type;
struct btf *btf;
u32 btf_id;
};
@ -1708,6 +1709,19 @@ struct bpf_struct_ops {
struct btf_func_model func_models[BPF_STRUCT_OPS_MAX_NR_MEMBERS];
};
/* Every member of a struct_ops type has an instance even a member is not
* an operator (function pointer). The "info" field will be assigned to
* prog->aux->ctx_arg_info of BPF struct_ops programs to provide the
* argument information required by the verifier to verify the program.
*
* btf_ctx_access() will lookup prog->aux->ctx_arg_info to find the
* corresponding entry for an given argument.
*/
struct bpf_struct_ops_arg_info {
struct bpf_ctx_arg_aux *info;
u32 cnt;
};
struct bpf_struct_ops_desc {
struct bpf_struct_ops *st_ops;
@ -1715,6 +1729,9 @@ struct bpf_struct_ops_desc {
const struct btf_type *value_type;
u32 type_id;
u32 value_id;
/* Collection of argument information for each member */
struct bpf_struct_ops_arg_info *arg_info;
};
enum bpf_struct_ops_state {
@ -1789,6 +1806,7 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
struct btf *btf,
struct bpf_verifier_log *log);
void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struct bpf_map *map);
void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc);
#else
#define register_bpf_struct_ops(st_ops, type) ({ (void *)(st_ops); 0; })
static inline bool bpf_try_module_get(const void *data, struct module *owner)
@ -1813,6 +1831,10 @@ static inline void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struc
{
}
static inline void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc)
{
}
#endif
#if defined(CONFIG_CGROUP_BPF) && defined(CONFIG_BPF_LSM)

View File

@ -495,6 +495,12 @@ static inline void *btf_id_set8_contains(const struct btf_id_set8 *set, u32 id)
return bsearch(&id, set->pairs, set->cnt, sizeof(set->pairs[0]), btf_id_cmp_func);
}
bool btf_param_match_suffix(const struct btf *btf,
const struct btf_param *arg,
const char *suffix);
int btf_ctx_arg_offset(const struct btf *btf, const struct btf_type *func_proto,
u32 arg_no);
struct bpf_verifier_log;
#if defined(CONFIG_BPF_JIT) && defined(CONFIG_BPF_SYSCALL)

View File

@ -116,17 +116,183 @@ static bool is_valid_value_type(struct btf *btf, s32 value_id,
return true;
}
#define MAYBE_NULL_SUFFIX "__nullable"
#define MAX_STUB_NAME 128
/* Return the type info of a stub function, if it exists.
*
* The name of a stub function is made up of the name of the struct_ops and
* the name of the function pointer member, separated by "__". For example,
* if the struct_ops type is named "foo_ops" and the function pointer
* member is named "bar", the stub function name would be "foo_ops__bar".
*/
static const struct btf_type *
find_stub_func_proto(const struct btf *btf, const char *st_op_name,
const char *member_name)
{
char stub_func_name[MAX_STUB_NAME];
const struct btf_type *func_type;
s32 btf_id;
int cp;
cp = snprintf(stub_func_name, MAX_STUB_NAME, "%s__%s",
st_op_name, member_name);
if (cp >= MAX_STUB_NAME) {
pr_warn("Stub function name too long\n");
return NULL;
}
btf_id = btf_find_by_name_kind(btf, stub_func_name, BTF_KIND_FUNC);
if (btf_id < 0)
return NULL;
func_type = btf_type_by_id(btf, btf_id);
if (!func_type)
return NULL;
return btf_type_by_id(btf, func_type->type); /* FUNC_PROTO */
}
/* Prepare argument info for every nullable argument of a member of a
* struct_ops type.
*
* Initialize a struct bpf_struct_ops_arg_info according to type info of
* the arguments of a stub function. (Check kCFI for more information about
* stub functions.)
*
* Each member in the struct_ops type has a struct bpf_struct_ops_arg_info
* to provide an array of struct bpf_ctx_arg_aux, which in turn provides
* the information that used by the verifier to check the arguments of the
* BPF struct_ops program assigned to the member. Here, we only care about
* the arguments that are marked as __nullable.
*
* The array of struct bpf_ctx_arg_aux is eventually assigned to
* prog->aux->ctx_arg_info of BPF struct_ops programs and passed to the
* verifier. (See check_struct_ops_btf_id())
*
* arg_info->info will be the list of struct bpf_ctx_arg_aux if success. If
* fails, it will be kept untouched.
*/
static int prepare_arg_info(struct btf *btf,
const char *st_ops_name,
const char *member_name,
const struct btf_type *func_proto,
struct bpf_struct_ops_arg_info *arg_info)
{
const struct btf_type *stub_func_proto, *pointed_type;
const struct btf_param *stub_args, *args;
struct bpf_ctx_arg_aux *info, *info_buf;
u32 nargs, arg_no, info_cnt = 0;
u32 arg_btf_id;
int offset;
stub_func_proto = find_stub_func_proto(btf, st_ops_name, member_name);
if (!stub_func_proto)
return 0;
/* Check if the number of arguments of the stub function is the same
* as the number of arguments of the function pointer.
*/
nargs = btf_type_vlen(func_proto);
if (nargs != btf_type_vlen(stub_func_proto)) {
pr_warn("the number of arguments of the stub function %s__%s does not match the number of arguments of the member %s of struct %s\n",
st_ops_name, member_name, member_name, st_ops_name);
return -EINVAL;
}
if (!nargs)
return 0;
args = btf_params(func_proto);
stub_args = btf_params(stub_func_proto);
info_buf = kcalloc(nargs, sizeof(*info_buf), GFP_KERNEL);
if (!info_buf)
return -ENOMEM;
/* Prepare info for every nullable argument */
info = info_buf;
for (arg_no = 0; arg_no < nargs; arg_no++) {
/* Skip arguments that is not suffixed with
* "__nullable".
*/
if (!btf_param_match_suffix(btf, &stub_args[arg_no],
MAYBE_NULL_SUFFIX))
continue;
/* Should be a pointer to struct */
pointed_type = btf_type_resolve_ptr(btf,
args[arg_no].type,
&arg_btf_id);
if (!pointed_type ||
!btf_type_is_struct(pointed_type)) {
pr_warn("stub function %s__%s has %s tagging to an unsupported type\n",
st_ops_name, member_name, MAYBE_NULL_SUFFIX);
goto err_out;
}
offset = btf_ctx_arg_offset(btf, func_proto, arg_no);
if (offset < 0) {
pr_warn("stub function %s__%s has an invalid trampoline ctx offset for arg#%u\n",
st_ops_name, member_name, arg_no);
goto err_out;
}
if (args[arg_no].type != stub_args[arg_no].type) {
pr_warn("arg#%u type in stub function %s__%s does not match with its original func_proto\n",
arg_no, st_ops_name, member_name);
goto err_out;
}
/* Fill the information of the new argument */
info->reg_type =
PTR_TRUSTED | PTR_TO_BTF_ID | PTR_MAYBE_NULL;
info->btf_id = arg_btf_id;
info->btf = btf;
info->offset = offset;
info++;
info_cnt++;
}
if (info_cnt) {
arg_info->info = info_buf;
arg_info->cnt = info_cnt;
} else {
kfree(info_buf);
}
return 0;
err_out:
kfree(info_buf);
return -EINVAL;
}
/* Clean up the arg_info in a struct bpf_struct_ops_desc. */
void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc)
{
struct bpf_struct_ops_arg_info *arg_info;
int i;
arg_info = st_ops_desc->arg_info;
for (i = 0; i < btf_type_vlen(st_ops_desc->type); i++)
kfree(arg_info[i].info);
kfree(arg_info);
}
int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
struct btf *btf,
struct bpf_verifier_log *log)
{
struct bpf_struct_ops *st_ops = st_ops_desc->st_ops;
struct bpf_struct_ops_arg_info *arg_info;
const struct btf_member *member;
const struct btf_type *t;
s32 type_id, value_id;
char value_name[128];
const char *mname;
int i;
int i, err;
if (strlen(st_ops->name) + VALUE_PREFIX_LEN >=
sizeof(value_name)) {
@ -160,6 +326,17 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
if (!is_valid_value_type(btf, value_id, t, value_name))
return -EINVAL;
arg_info = kcalloc(btf_type_vlen(t), sizeof(*arg_info),
GFP_KERNEL);
if (!arg_info)
return -ENOMEM;
st_ops_desc->arg_info = arg_info;
st_ops_desc->type = t;
st_ops_desc->type_id = type_id;
st_ops_desc->value_id = value_id;
st_ops_desc->value_type = btf_type_by_id(btf, value_id);
for_each_member(i, t, member) {
const struct btf_type *func_proto;
@ -167,40 +344,52 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
if (!*mname) {
pr_warn("anon member in struct %s is not supported\n",
st_ops->name);
return -EOPNOTSUPP;
err = -EOPNOTSUPP;
goto errout;
}
if (__btf_member_bitfield_size(t, member)) {
pr_warn("bit field member %s in struct %s is not supported\n",
mname, st_ops->name);
return -EOPNOTSUPP;
err = -EOPNOTSUPP;
goto errout;
}
func_proto = btf_type_resolve_func_ptr(btf,
member->type,
NULL);
if (func_proto &&
btf_distill_func_proto(log, btf,
if (!func_proto)
continue;
if (btf_distill_func_proto(log, btf,
func_proto, mname,
&st_ops->func_models[i])) {
pr_warn("Error in parsing func ptr %s in struct %s\n",
mname, st_ops->name);
return -EINVAL;
err = -EINVAL;
goto errout;
}
err = prepare_arg_info(btf, st_ops->name, mname,
func_proto,
arg_info + i);
if (err)
goto errout;
}
if (st_ops->init(btf)) {
pr_warn("Error in init bpf_struct_ops %s\n",
st_ops->name);
return -EINVAL;
err = -EINVAL;
goto errout;
}
st_ops_desc->type_id = type_id;
st_ops_desc->type = t;
st_ops_desc->value_id = value_id;
st_ops_desc->value_type = btf_type_by_id(btf, value_id);
return 0;
errout:
bpf_struct_ops_desc_release(st_ops_desc);
return err;
}
static int bpf_struct_ops_map_get_next_key(struct bpf_map *map, void *key,

View File

@ -1699,6 +1699,13 @@ static void btf_free_struct_meta_tab(struct btf *btf)
static void btf_free_struct_ops_tab(struct btf *btf)
{
struct btf_struct_ops_tab *tab = btf->struct_ops_tab;
u32 i;
if (!tab)
return;
for (i = 0; i < tab->cnt; i++)
bpf_struct_ops_desc_release(&tab->ops[i]);
kfree(tab);
btf->struct_ops_tab = NULL;
@ -6130,6 +6137,26 @@ static bool prog_args_trusted(const struct bpf_prog *prog)
}
}
int btf_ctx_arg_offset(const struct btf *btf, const struct btf_type *func_proto,
u32 arg_no)
{
const struct btf_param *args;
const struct btf_type *t;
int off = 0, i;
u32 sz;
args = btf_params(func_proto);
for (i = 0; i < arg_no; i++) {
t = btf_type_by_id(btf, args[i].type);
t = btf_resolve_size(btf, t, &sz);
if (IS_ERR(t))
return PTR_ERR(t);
off += roundup(sz, 8);
}
return off;
}
bool btf_ctx_access(int off, int size, enum bpf_access_type type,
const struct bpf_prog *prog,
struct bpf_insn_access_aux *info)
@ -6266,7 +6293,7 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
}
info->reg_type = ctx_arg_info->reg_type;
info->btf = btf_vmlinux;
info->btf = ctx_arg_info->btf ? : btf_vmlinux;
info->btf_id = ctx_arg_info->btf_id;
return true;
}
@ -8904,3 +8931,21 @@ errout:
}
EXPORT_SYMBOL_GPL(__register_bpf_struct_ops);
#endif
bool btf_param_match_suffix(const struct btf *btf,
const struct btf_param *arg,
const char *suffix)
{
int suffix_len = strlen(suffix), len;
const char *param_name;
/* In the future, this can be ported to use BTF tagging */
param_name = btf_name_by_offset(btf, arg->name_off);
if (str_is_empty(param_name))
return false;
len = strlen(param_name);
if (len <= suffix_len)
return false;
param_name += len - suffix_len;
return !strncmp(param_name, suffix, suffix_len);
}

View File

@ -10682,24 +10682,6 @@ static bool is_kfunc_rcu_protected(struct bpf_kfunc_call_arg_meta *meta)
return meta->kfunc_flags & KF_RCU_PROTECTED;
}
static bool __kfunc_param_match_suffix(const struct btf *btf,
const struct btf_param *arg,
const char *suffix)
{
int suffix_len = strlen(suffix), len;
const char *param_name;
/* In the future, this can be ported to use BTF tagging */
param_name = btf_name_by_offset(btf, arg->name_off);
if (str_is_empty(param_name))
return false;
len = strlen(param_name);
if (len < suffix_len)
return false;
param_name += len - suffix_len;
return !strncmp(param_name, suffix, suffix_len);
}
static bool is_kfunc_arg_mem_size(const struct btf *btf,
const struct btf_param *arg,
const struct bpf_reg_state *reg)
@ -10710,7 +10692,7 @@ static bool is_kfunc_arg_mem_size(const struct btf *btf,
if (!btf_type_is_scalar(t) || reg->type != SCALAR_VALUE)
return false;
return __kfunc_param_match_suffix(btf, arg, "__sz");
return btf_param_match_suffix(btf, arg, "__sz");
}
static bool is_kfunc_arg_const_mem_size(const struct btf *btf,
@ -10723,47 +10705,47 @@ static bool is_kfunc_arg_const_mem_size(const struct btf *btf,
if (!btf_type_is_scalar(t) || reg->type != SCALAR_VALUE)
return false;
return __kfunc_param_match_suffix(btf, arg, "__szk");
return btf_param_match_suffix(btf, arg, "__szk");
}
static bool is_kfunc_arg_optional(const struct btf *btf, const struct btf_param *arg)
{
return __kfunc_param_match_suffix(btf, arg, "__opt");
return btf_param_match_suffix(btf, arg, "__opt");
}
static bool is_kfunc_arg_constant(const struct btf *btf, const struct btf_param *arg)
{
return __kfunc_param_match_suffix(btf, arg, "__k");
return btf_param_match_suffix(btf, arg, "__k");
}
static bool is_kfunc_arg_ignore(const struct btf *btf, const struct btf_param *arg)
{
return __kfunc_param_match_suffix(btf, arg, "__ign");
return btf_param_match_suffix(btf, arg, "__ign");
}
static bool is_kfunc_arg_alloc_obj(const struct btf *btf, const struct btf_param *arg)
{
return __kfunc_param_match_suffix(btf, arg, "__alloc");
return btf_param_match_suffix(btf, arg, "__alloc");
}
static bool is_kfunc_arg_uninit(const struct btf *btf, const struct btf_param *arg)
{
return __kfunc_param_match_suffix(btf, arg, "__uninit");
return btf_param_match_suffix(btf, arg, "__uninit");
}
static bool is_kfunc_arg_refcounted_kptr(const struct btf *btf, const struct btf_param *arg)
{
return __kfunc_param_match_suffix(btf, arg, "__refcounted_kptr");
return btf_param_match_suffix(btf, arg, "__refcounted_kptr");
}
static bool is_kfunc_arg_nullable(const struct btf *btf, const struct btf_param *arg)
{
return __kfunc_param_match_suffix(btf, arg, "__nullable");
return btf_param_match_suffix(btf, arg, "__nullable");
}
static bool is_kfunc_arg_const_str(const struct btf *btf, const struct btf_param *arg)
{
return __kfunc_param_match_suffix(btf, arg, "__str");
return btf_param_match_suffix(btf, arg, "__str");
}
static bool is_kfunc_arg_scalar_with_name(const struct btf *btf,
@ -20437,6 +20419,12 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
}
}
/* btf_ctx_access() used this to provide argument type info */
prog->aux->ctx_arg_info =
st_ops_desc->arg_info[member_idx].info;
prog->aux->ctx_arg_info_size =
st_ops_desc->arg_info[member_idx].cnt;
prog->aux->attach_func_proto = func_proto;
prog->aux->attach_func_name = mname;
env->ops = st_ops->verifier_ops;

View File

@ -555,7 +555,11 @@ static int bpf_dummy_reg(void *kdata)
{
struct bpf_testmod_ops *ops = kdata;
ops->test_2(4, 3);
/* Some test cases (ex. struct_ops_maybe_null) may not have test_2
* initialized, so we need to check for NULL.
*/
if (ops->test_2)
ops->test_2(4, 3);
return 0;
}
@ -573,9 +577,16 @@ static void bpf_testmod_test_2(int a, int b)
{
}
static int bpf_testmod_ops__test_maybe_null(int dummy,
struct task_struct *task__nullable)
{
return 0;
}
static struct bpf_testmod_ops __bpf_testmod_ops = {
.test_1 = bpf_testmod_test_1,
.test_2 = bpf_testmod_test_2,
.test_maybe_null = bpf_testmod_ops__test_maybe_null,
};
struct bpf_struct_ops bpf_bpf_testmod_ops = {

View File

@ -5,6 +5,8 @@
#include <linux/types.h>
struct task_struct;
struct bpf_testmod_test_read_ctx {
char *buf;
loff_t off;
@ -31,6 +33,8 @@ struct bpf_iter_testmod_seq {
struct bpf_testmod_ops {
int (*test_1)(void);
void (*test_2)(int a, int b);
/* Used to test nullable arguments. */
int (*test_maybe_null)(int dummy, struct task_struct *task);
};
#endif /* _BPF_TESTMOD_H */

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
#include <test_progs.h>
#include "struct_ops_maybe_null.skel.h"
#include "struct_ops_maybe_null_fail.skel.h"
/* Test that the verifier accepts a program that access a nullable pointer
* with a proper check.
*/
static void maybe_null(void)
{
struct struct_ops_maybe_null *skel;
skel = struct_ops_maybe_null__open_and_load();
if (!ASSERT_OK_PTR(skel, "struct_ops_module_open_and_load"))
return;
struct_ops_maybe_null__destroy(skel);
}
/* Test that the verifier rejects a program that access a nullable pointer
* without a check beforehand.
*/
static void maybe_null_fail(void)
{
struct struct_ops_maybe_null_fail *skel;
skel = struct_ops_maybe_null_fail__open_and_load();
if (ASSERT_ERR_PTR(skel, "struct_ops_module_fail__open_and_load"))
return;
struct_ops_maybe_null_fail__destroy(skel);
}
void test_struct_ops_maybe_null(void)
{
/* The verifier verifies the programs at load time, so testing both
* programs in the same compile-unit is complicated. We run them in
* separate objects to simplify the testing.
*/
if (test__start_subtest("maybe_null"))
maybe_null();
if (test__start_subtest("maybe_null_fail"))
maybe_null_fail();
}

View File

@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "../bpf_testmod/bpf_testmod.h"
char _license[] SEC("license") = "GPL";
pid_t tgid = 0;
/* This is a test BPF program that uses struct_ops to access an argument
* that may be NULL. This is a test for the verifier to ensure that it can
* rip PTR_MAYBE_NULL correctly.
*/
SEC("struct_ops/test_maybe_null")
int BPF_PROG(test_maybe_null, int dummy,
struct task_struct *task)
{
if (task)
tgid = task->tgid;
return 0;
}
SEC(".struct_ops.link")
struct bpf_testmod_ops testmod_1 = {
.test_maybe_null = (void *)test_maybe_null,
};

View File

@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "../bpf_testmod/bpf_testmod.h"
char _license[] SEC("license") = "GPL";
pid_t tgid = 0;
SEC("struct_ops/test_maybe_null_struct_ptr")
int BPF_PROG(test_maybe_null_struct_ptr, int dummy,
struct task_struct *task)
{
tgid = task->tgid;
return 0;
}
SEC(".struct_ops.link")
struct bpf_testmod_ops testmod_struct_ptr = {
.test_maybe_null = (void *)test_maybe_null_struct_ptr,
};