Merge branch 'trusted-ptr_to_btf_id-arg-support-in-global-subprogs'

Andrii Nakryiko says:

====================
Trusted PTR_TO_BTF_ID arg support in global subprogs

This patch set follows recent changes that added btf_decl_tag-based argument
annotation support for global subprogs. This time we add ability to pass
PTR_TO_BTF_ID (BTF-aware kernel pointers) arguments into global subprograms.
We support explicitly trusted arguments only, for now.

Patch #1 adds logic for arg:trusted tag support on the verifier side. Default
semantic of such arguments is non-NULL, enforced on caller side. But patch #2
adds arg:nullable tag that can be combined with arg:trusted to make callee
explicitly do the NULL check, which helps implement "optional" PTR_TO_BTF_ID
arguments.

Patch #3 adds libbpf-side __arg_trusted and __arg_nullable macros.

Patch #4 adds a bunch of tests validating __arg_trusted in combination with
__arg_nullable.

v2->v3:
  - went back to arg:nullable and __arg_nullable naming;
  - rebased on latest bpf-next after prepartory patches landed;
v1->v2:
  - added fix up to type enforcement changes, landed earlier;
  - dropped bpf_core_cast() changes, will post them separately, as they now
    are not used in added tests;
  - dropped arg:untrusted support (Alexei);
  - renamed arg:nullable to arg:maybe_null (Alexei);
  - and also added task_struct___local flavor tests (Alexei).
====================

Link: https://lore.kernel.org/r/20240130000648.2144827-1-andrii@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2024-01-30 09:41:51 -08:00
commit 4d8ebe1304
6 changed files with 281 additions and 13 deletions

View File

@ -610,6 +610,7 @@ struct bpf_subprog_arg_info {
enum bpf_arg_type arg_type;
union {
u32 mem_size;
u32 btf_id;
};
};

View File

@ -6985,9 +6985,78 @@ static bool btf_is_dynptr_ptr(const struct btf *btf, const struct btf_type *t)
return false;
}
struct bpf_cand_cache {
const char *name;
u32 name_len;
u16 kind;
u16 cnt;
struct {
const struct btf *btf;
u32 id;
} cands[];
};
static DEFINE_MUTEX(cand_cache_mutex);
static struct bpf_cand_cache *
bpf_core_find_cands(struct bpf_core_ctx *ctx, u32 local_type_id);
static int btf_get_ptr_to_btf_id(struct bpf_verifier_log *log, int arg_idx,
const struct btf *btf, const struct btf_type *t)
{
struct bpf_cand_cache *cc;
struct bpf_core_ctx ctx = {
.btf = btf,
.log = log,
};
u32 kern_type_id, type_id;
int err = 0;
/* skip PTR and modifiers */
type_id = t->type;
t = btf_type_by_id(btf, t->type);
while (btf_type_is_modifier(t)) {
type_id = t->type;
t = btf_type_by_id(btf, t->type);
}
mutex_lock(&cand_cache_mutex);
cc = bpf_core_find_cands(&ctx, type_id);
if (IS_ERR(cc)) {
err = PTR_ERR(cc);
bpf_log(log, "arg#%d reference type('%s %s') candidate matching error: %d\n",
arg_idx, btf_type_str(t), __btf_name_by_offset(btf, t->name_off),
err);
goto cand_cache_unlock;
}
if (cc->cnt != 1) {
bpf_log(log, "arg#%d reference type('%s %s') %s\n",
arg_idx, btf_type_str(t), __btf_name_by_offset(btf, t->name_off),
cc->cnt == 0 ? "has no matches" : "is ambiguous");
err = cc->cnt == 0 ? -ENOENT : -ESRCH;
goto cand_cache_unlock;
}
if (btf_is_module(cc->cands[0].btf)) {
bpf_log(log, "arg#%d reference type('%s %s') points to kernel module type (unsupported)\n",
arg_idx, btf_type_str(t), __btf_name_by_offset(btf, t->name_off));
err = -EOPNOTSUPP;
goto cand_cache_unlock;
}
kern_type_id = cc->cands[0].id;
cand_cache_unlock:
mutex_unlock(&cand_cache_mutex);
if (err)
return err;
return kern_type_id;
}
enum btf_arg_tag {
ARG_TAG_CTX = 0x1,
ARG_TAG_NONNULL = 0x2,
ARG_TAG_TRUSTED = 0x4,
ARG_TAG_NULLABLE = 0x8,
};
/* Process BTF of a function to produce high-level expectation of function
@ -7089,8 +7158,12 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
if (strcmp(tag, "ctx") == 0) {
tags |= ARG_TAG_CTX;
} else if (strcmp(tag, "trusted") == 0) {
tags |= ARG_TAG_TRUSTED;
} else if (strcmp(tag, "nonnull") == 0) {
tags |= ARG_TAG_NONNULL;
} else if (strcmp(tag, "nullable") == 0) {
tags |= ARG_TAG_NULLABLE;
} else {
bpf_log(log, "arg#%d has unsupported set of tags\n", i);
return -EOPNOTSUPP;
@ -7127,9 +7200,32 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
sub->args[i].arg_type = ARG_PTR_TO_DYNPTR | MEM_RDONLY;
continue;
}
if (tags & ARG_TAG_TRUSTED) {
int kern_type_id;
if (tags & ARG_TAG_NONNULL) {
bpf_log(log, "arg#%d has invalid combination of tags\n", i);
return -EINVAL;
}
kern_type_id = btf_get_ptr_to_btf_id(log, i, btf, t);
if (kern_type_id < 0)
return kern_type_id;
sub->args[i].arg_type = ARG_PTR_TO_BTF_ID | PTR_TRUSTED;
if (tags & ARG_TAG_NULLABLE)
sub->args[i].arg_type |= PTR_MAYBE_NULL;
sub->args[i].btf_id = kern_type_id;
continue;
}
if (is_global) { /* generic user data pointer */
u32 mem_size;
if (tags & ARG_TAG_NULLABLE) {
bpf_log(log, "arg#%d has invalid combination of tags\n", i);
return -EINVAL;
}
t = btf_type_skip_modifiers(btf, t->type, NULL);
ref_t = btf_resolve_size(btf, t, &mem_size);
if (IS_ERR(ref_t)) {
@ -8229,17 +8325,6 @@ size_t bpf_core_essential_name_len(const char *name)
return n;
}
struct bpf_cand_cache {
const char *name;
u32 name_len;
u16 kind;
u16 cnt;
struct {
const struct btf *btf;
u32 id;
} cands[];
};
static void bpf_free_cands(struct bpf_cand_cache *cands)
{
if (!cands->cnt)
@ -8260,8 +8345,6 @@ static struct bpf_cand_cache *vmlinux_cand_cache[VMLINUX_CAND_CACHE_SIZE];
#define MODULE_CAND_CACHE_SIZE 31
static struct bpf_cand_cache *module_cand_cache[MODULE_CAND_CACHE_SIZE];
static DEFINE_MUTEX(cand_cache_mutex);
static void __print_cand_cache(struct bpf_verifier_log *log,
struct bpf_cand_cache **cache,
int cache_size)

View File

@ -9336,6 +9336,18 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
ret = process_dynptr_func(env, regno, -1, arg->arg_type, 0);
if (ret)
return ret;
} else if (base_type(arg->arg_type) == ARG_PTR_TO_BTF_ID) {
struct bpf_call_arg_meta meta;
int err;
if (register_is_null(reg) && type_may_be_null(arg->arg_type))
continue;
memset(&meta, 0, sizeof(meta)); /* leave func_id as zero */
err = check_reg_type(env, regno, arg->arg_type, &arg->btf_id, &meta);
err = err ?: check_func_arg_reg_off(env, reg, regno, arg->arg_type);
if (err)
return err;
} else {
bpf_log(log, "verifier bug: unrecognized arg#%d type %d\n",
i, arg->arg_type);
@ -20137,6 +20149,18 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
mark_reg_known_zero(env, regs, i);
reg->mem_size = arg->mem_size;
reg->id = ++env->id_gen;
} else if (base_type(arg->arg_type) == ARG_PTR_TO_BTF_ID) {
reg->type = PTR_TO_BTF_ID;
if (arg->arg_type & PTR_MAYBE_NULL)
reg->type |= PTR_MAYBE_NULL;
if (arg->arg_type & PTR_UNTRUSTED)
reg->type |= PTR_UNTRUSTED;
if (arg->arg_type & PTR_TRUSTED)
reg->type |= PTR_TRUSTED;
mark_reg_known_zero(env, regs, i);
reg->btf = bpf_get_btf_vmlinux(); /* can't fail at this point */
reg->btf_id = arg->btf_id;
reg->id = ++env->id_gen;
} else {
WARN_ONCE(1, "BUG: unhandled arg#%d type %d\n",
i - BPF_REG_1, arg->arg_type);

View File

@ -190,6 +190,8 @@ enum libbpf_tristate {
#define __arg_ctx __attribute__((btf_decl_tag("arg:ctx")))
#define __arg_nonnull __attribute((btf_decl_tag("arg:nonnull")))
#define __arg_nullable __attribute((btf_decl_tag("arg:nullable")))
#define __arg_trusted __attribute((btf_decl_tag("arg:trusted")))
#ifndef ___bpf_concat
#define ___bpf_concat(a, b) a ## b

View File

@ -28,6 +28,7 @@
#include "verifier_div0.skel.h"
#include "verifier_div_overflow.skel.h"
#include "verifier_global_subprogs.skel.h"
#include "verifier_global_ptr_args.skel.h"
#include "verifier_gotol.skel.h"
#include "verifier_helper_access_var_len.skel.h"
#include "verifier_helper_packet_access.skel.h"
@ -140,6 +141,7 @@ void test_verifier_direct_stack_access_wraparound(void) { RUN(verifier_direct_st
void test_verifier_div0(void) { RUN(verifier_div0); }
void test_verifier_div_overflow(void) { RUN(verifier_div_overflow); }
void test_verifier_global_subprogs(void) { RUN(verifier_global_subprogs); }
void test_verifier_global_ptr_args(void) { RUN(verifier_global_ptr_args); }
void test_verifier_gotol(void) { RUN(verifier_gotol); }
void test_verifier_helper_access_var_len(void) { RUN(verifier_helper_access_var_len); }
void test_verifier_helper_packet_access(void) { RUN(verifier_helper_packet_access); }

View File

@ -0,0 +1,156 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "bpf_misc.h"
#include "xdp_metadata.h"
#include "bpf_kfuncs.h"
extern struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym __weak;
extern void bpf_task_release(struct task_struct *p) __ksym __weak;
__weak int subprog_trusted_task_nullable(struct task_struct *task __arg_trusted __arg_nullable)
{
if (!task)
return 0;
return task->pid + task->tgid;
}
SEC("?kprobe")
__success __log_level(2)
__msg("Validating subprog_trusted_task_nullable() func#1...")
__msg(": R1=trusted_ptr_or_null_task_struct(")
int trusted_task_arg_nullable(void *ctx)
{
struct task_struct *t = bpf_get_current_task_btf();
return subprog_trusted_task_nullable(t) + subprog_trusted_task_nullable(NULL);
}
__weak int subprog_trusted_task_nonnull(struct task_struct *task __arg_trusted)
{
return task->pid + task->tgid;
}
SEC("?kprobe")
__failure __log_level(2)
__msg("R1 type=scalar expected=ptr_, trusted_ptr_, rcu_ptr_")
__msg("Caller passes invalid args into func#1 ('subprog_trusted_task_nonnull')")
int trusted_task_arg_nonnull_fail1(void *ctx)
{
return subprog_trusted_task_nonnull(NULL);
}
SEC("?tp_btf/task_newtask")
__failure __log_level(2)
__msg("R1 type=ptr_or_null_ expected=ptr_, trusted_ptr_, rcu_ptr_")
__msg("Caller passes invalid args into func#1 ('subprog_trusted_task_nonnull')")
int trusted_task_arg_nonnull_fail2(void *ctx)
{
struct task_struct *t = bpf_get_current_task_btf();
struct task_struct *nullable;
int res;
nullable = bpf_task_acquire(t);
/* should fail, PTR_TO_BTF_ID_OR_NULL */
res = subprog_trusted_task_nonnull(nullable);
if (nullable)
bpf_task_release(nullable);
return res;
}
SEC("?kprobe")
__success __log_level(2)
__msg("Validating subprog_trusted_task_nonnull() func#1...")
__msg(": R1=trusted_ptr_task_struct(")
int trusted_task_arg_nonnull(void *ctx)
{
struct task_struct *t = bpf_get_current_task_btf();
return subprog_trusted_task_nonnull(t);
}
struct task_struct___local {} __attribute__((preserve_access_index));
__weak int subprog_nullable_task_flavor(
struct task_struct___local *task __arg_trusted __arg_nullable)
{
char buf[16];
if (!task)
return 0;
return bpf_copy_from_user_task(&buf, sizeof(buf), NULL, (void *)task, 0);
}
SEC("?uprobe.s")
__success __log_level(2)
__msg("Validating subprog_nullable_task_flavor() func#1...")
__msg(": R1=trusted_ptr_or_null_task_struct(")
int flavor_ptr_nullable(void *ctx)
{
struct task_struct___local *t = (void *)bpf_get_current_task_btf();
return subprog_nullable_task_flavor(t);
}
__weak int subprog_nonnull_task_flavor(struct task_struct___local *task __arg_trusted)
{
char buf[16];
return bpf_copy_from_user_task(&buf, sizeof(buf), NULL, (void *)task, 0);
}
SEC("?uprobe.s")
__success __log_level(2)
__msg("Validating subprog_nonnull_task_flavor() func#1...")
__msg(": R1=trusted_ptr_task_struct(")
int flavor_ptr_nonnull(void *ctx)
{
struct task_struct *t = bpf_get_current_task_btf();
return subprog_nonnull_task_flavor((void *)t);
}
__weak int subprog_trusted_destroy(struct task_struct *task __arg_trusted)
{
bpf_task_release(task); /* should be rejected */
return 0;
}
SEC("?tp_btf/task_newtask")
__failure __log_level(2)
__msg("release kernel function bpf_task_release expects refcounted PTR_TO_BTF_ID")
int BPF_PROG(trusted_destroy_fail, struct task_struct *task, u64 clone_flags)
{
return subprog_trusted_destroy(task);
}
__weak int subprog_trusted_acq_rel(struct task_struct *task __arg_trusted)
{
struct task_struct *owned;
owned = bpf_task_acquire(task);
if (!owned)
return 0;
bpf_task_release(owned); /* this one is OK, we acquired it locally */
return 0;
}
SEC("?tp_btf/task_newtask")
__success __log_level(2)
int BPF_PROG(trusted_acq_rel, struct task_struct *task, u64 clone_flags)
{
return subprog_trusted_acq_rel(task);
}
char _license[] SEC("license") = "GPL";