blob: a487aa68f2eef7ee31db626fb34a34f194b960eb [file]
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#include <bpf/btf.h>
#include <linux/btf.h>
#include <test_progs.h>
#define SPIN_LOCK 2
#define LIST_HEAD 3
#define LIST_NODE 4
/* Keep in sync with BTF_MAX_OWNERSHIP_DEPTH. */
#define MAX_OWNERSHIP_DEPTH 8
static struct btf *init_btf(void)
{
struct btf *btf;
int id;
btf = btf__new_empty();
if (!ASSERT_OK_PTR(btf, "btf__new_empty"))
return NULL;
id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
if (!ASSERT_EQ(id, 1, "btf__add_int"))
goto err_out;
id = btf__add_struct(btf, "bpf_spin_lock", 4);
if (!ASSERT_EQ(id, SPIN_LOCK, "btf__add_struct bpf_spin_lock"))
goto err_out;
id = btf__add_struct(btf, "bpf_list_head", 16);
if (!ASSERT_EQ(id, LIST_HEAD, "btf__add_struct bpf_list_head"))
goto err_out;
id = btf__add_struct(btf, "bpf_list_node", 24);
if (!ASSERT_EQ(id, LIST_NODE, "btf__add_struct bpf_list_node"))
goto err_out;
return btf;
err_out:
btf__free(btf);
return NULL;
}
static int add_local_kptr(struct btf *btf, int pointee_id, const char *tag)
{
int id;
id = btf__add_type_tag(btf, tag, pointee_id);
if (!ASSERT_GT(id, 0, "btf__add_type_tag"))
return id;
id = btf__add_ptr(btf, id);
ASSERT_GT(id, 0, "btf__add_ptr");
return id;
}
static void test_self_cycle(const char *tag, int expected_err)
{
struct btf *btf;
int id, err;
btf = init_btf();
if (!ASSERT_OK_PTR(btf, "init_btf"))
return;
id = add_local_kptr(btf, 7, tag);
if (id <= 0)
goto out;
id = btf__add_struct(btf, "self_cycle", 8);
if (!ASSERT_EQ(id, 7, "btf__add_struct self_cycle"))
goto out;
err = btf__add_field(btf, "next", 6, 0, 0);
if (!ASSERT_OK(err, "btf__add_field self_cycle::next"))
goto out;
err = btf__load_into_kernel(btf);
ASSERT_EQ(err, expected_err, "check btf");
out:
btf__free(btf);
}
static void test_aba_cycle(void)
{
struct btf *btf;
int id, err;
btf = init_btf();
if (!ASSERT_OK_PTR(btf, "init_btf"))
return;
id = add_local_kptr(btf, 10, "kptr");
if (id <= 0)
goto out;
id = add_local_kptr(btf, 9, "kptr");
if (id <= 0)
goto out;
id = btf__add_struct(btf, "cycle_a", 8);
if (!ASSERT_EQ(id, 9, "btf__add_struct cycle_a"))
goto out;
err = btf__add_field(btf, "b", 6, 0, 0);
if (!ASSERT_OK(err, "btf__add_field cycle_a::b"))
goto out;
id = btf__add_struct(btf, "cycle_b", 8);
if (!ASSERT_EQ(id, 10, "btf__add_struct cycle_b"))
goto out;
err = btf__add_field(btf, "a", 8, 0, 0);
if (!ASSERT_OK(err, "btf__add_field cycle_b::a"))
goto out;
err = btf__load_into_kernel(btf);
ASSERT_EQ(err, -ELOOP, "check btf");
out:
btf__free(btf);
}
static void test_mixed_cycle(void)
{
struct btf *btf;
int id, err;
btf = init_btf();
if (!ASSERT_OK_PTR(btf, "init_btf"))
return;
id = add_local_kptr(btf, 7, "kptr");
if (id <= 0)
goto out;
id = btf__add_struct(btf, "mixed_owner", 20);
if (!ASSERT_EQ(id, 7, "btf__add_struct mixed_owner"))
goto out;
err = btf__add_field(btf, "root", LIST_HEAD, 0, 0);
if (!ASSERT_OK(err, "btf__add_field mixed_owner::root"))
goto out;
err = btf__add_field(btf, "lock", SPIN_LOCK, 128, 0);
if (!ASSERT_OK(err, "btf__add_field mixed_owner::lock"))
goto out;
id = btf__add_decl_tag(btf, "contains:mixed_node:node", 7, 0);
if (!ASSERT_EQ(id, 8, "btf__add_decl_tag mixed_owner"))
goto out;
id = btf__add_struct(btf, "mixed_node", 32);
if (!ASSERT_EQ(id, 9, "btf__add_struct mixed_node"))
goto out;
err = btf__add_field(btf, "node", LIST_NODE, 0, 0);
if (!ASSERT_OK(err, "btf__add_field mixed_node::node"))
goto out;
err = btf__add_field(btf, "owner", 6, 192, 0);
if (!ASSERT_OK(err, "btf__add_field mixed_node::owner"))
goto out;
err = btf__load_into_kernel(btf);
ASSERT_EQ(err, -ELOOP, "check btf");
out:
btf__free(btf);
}
static void test_acyclic_depth(int depth, bool child_first, bool shared_suffix, int expected_err)
{
int ptr_id[MAX_OWNERSHIP_DEPTH + 1];
int first_struct_id;
struct btf *btf;
int id, err, i, n, pointee_id;
btf = init_btf();
if (!ASSERT_OK_PTR(btf, "init_btf"))
return;
first_struct_id = 5 + 2 * depth;
for (i = 0; i < depth; i++) {
if (i == depth - 1)
pointee_id = first_struct_id + depth;
else
pointee_id = first_struct_id + (child_first ? depth - 2 - i : i + 1);
ptr_id[i] = add_local_kptr(btf, pointee_id, "kptr");
if (ptr_id[i] <= 0)
goto out;
}
for (n = 0; n < depth; n++) {
char name[32];
int offset = 0;
i = child_first ? depth - 1 - n : n;
snprintf(name, sizeof(name), "owner_%d", i);
id = btf__add_struct(btf, name, shared_suffix && !i ? 16 : 8);
if (!ASSERT_EQ(id, first_struct_id + n, "btf__add_struct owner"))
goto out;
if (shared_suffix && !i) {
/*
* Visit the shared suffix through the shorter path before
* reaching it again with less remaining depth.
*/
err = btf__add_field(btf, "suffix", ptr_id[1], 0, 0);
if (!ASSERT_OK(err, "btf__add_field owner::suffix"))
goto out;
offset = 64;
}
err = btf__add_field(btf, "next", ptr_id[i], offset, 0);
if (!ASSERT_OK(err, "btf__add_field owner::next"))
goto out;
}
id = btf__add_struct(btf, "plain_leaf", 4);
if (!ASSERT_EQ(id, first_struct_id + depth, "btf__add_struct plain_leaf"))
goto out;
err = btf__load_into_kernel(btf);
ASSERT_EQ(err, expected_err, "check btf");
out:
btf__free(btf);
}
static void test_graph_depth(bool rbtree, int depth, int expected_err)
{
int root_type = LIST_HEAD, node_type = LIST_NODE, node_size = 24;
int id, err, i, lock_off, root_off, size;
struct btf *btf;
btf = init_btf();
if (!ASSERT_OK_PTR(btf, "init_btf"))
return;
if (rbtree) {
root_type = btf__add_struct(btf, "bpf_rb_root", 16);
if (!ASSERT_GT(root_type, 0, "btf__add_struct bpf_rb_root"))
goto out;
node_type = btf__add_struct(btf, "bpf_rb_node", 32);
if (!ASSERT_GT(node_type, 0, "btf__add_struct bpf_rb_node"))
goto out;
node_size = 32;
}
for (i = 0; i < depth; i++) {
char name[32], tag[64];
lock_off = i ? node_size : 0;
root_off = lock_off + 8;
size = i == depth - 1 ? node_size : root_off + 16;
snprintf(name, sizeof(name), "graph_owner_%d", i);
id = btf__add_struct(btf, name, size);
if (!ASSERT_GT(id, 0, "btf__add_struct graph_owner"))
goto out;
if (i) {
err = btf__add_field(btf, "node", node_type, 0, 0);
if (!ASSERT_OK(err, "btf__add_field graph_owner::node"))
goto out;
}
if (i == depth - 1)
continue;
err = btf__add_field(btf, "lock", SPIN_LOCK, lock_off * 8, 0);
if (!ASSERT_OK(err, "btf__add_field graph_owner::lock"))
goto out;
err = btf__add_field(btf, "root", root_type, root_off * 8, 0);
if (!ASSERT_OK(err, "btf__add_field graph_owner::root"))
goto out;
snprintf(tag, sizeof(tag), "contains:graph_owner_%d:node", i + 1);
err = btf__add_decl_tag(btf, tag, id, i ? 2 : 1);
if (!ASSERT_GT(err, 0, "btf__add_decl_tag graph_owner"))
goto out;
}
err = btf__load_into_kernel(btf);
ASSERT_EQ(err, expected_err, "check btf");
out:
btf__free(btf);
}
void test_local_kptr_ownership(void)
{
if (test__start_subtest("self_cycle"))
test_self_cycle("kptr", -ELOOP);
if (test__start_subtest("untrusted_self_cycle"))
test_self_cycle("kptr_untrusted", 0);
if (test__start_subtest("percpu_self_cycle"))
test_self_cycle("percpu_kptr", -ELOOP);
if (test__start_subtest("ABA_cycle"))
test_aba_cycle();
if (test__start_subtest("mixed_graph_root_cycle"))
test_mixed_cycle();
if (test__start_subtest("max_acyclic"))
test_acyclic_depth(MAX_OWNERSHIP_DEPTH, false, false, 0);
if (test__start_subtest("too_deep_acyclic"))
test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, false, false, -ELOOP);
if (test__start_subtest("max_acyclic_child_first"))
test_acyclic_depth(MAX_OWNERSHIP_DEPTH, true, false, 0);
if (test__start_subtest("too_deep_acyclic_child_first"))
test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, true, false, -ELOOP);
if (test__start_subtest("max_acyclic_shared_suffix"))
test_acyclic_depth(MAX_OWNERSHIP_DEPTH, false, true, 0);
if (test__start_subtest("too_deep_acyclic_shared_suffix"))
test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, false, true, -ELOOP);
if (test__start_subtest("list_three_types"))
test_graph_depth(false, 3, 0);
if (test__start_subtest("list_four_types"))
test_graph_depth(false, 4, 0);
if (test__start_subtest("list_max_depth"))
test_graph_depth(false, MAX_OWNERSHIP_DEPTH, 0);
if (test__start_subtest("list_too_deep"))
test_graph_depth(false, MAX_OWNERSHIP_DEPTH + 1, -ELOOP);
if (test__start_subtest("rbtree_three_types"))
test_graph_depth(true, 3, 0);
if (test__start_subtest("rbtree_four_types"))
test_graph_depth(true, 4, 0);
if (test__start_subtest("rbtree_max_depth"))
test_graph_depth(true, MAX_OWNERSHIP_DEPTH, 0);
if (test__start_subtest("rbtree_too_deep"))
test_graph_depth(true, MAX_OWNERSHIP_DEPTH + 1, -ELOOP);
}