root/tools/testing/selftests/bpf/prog_tests/bpftool_maps_access.c
// SPDX-License-Identifier: GPL-2.0-only

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <linux/bpf.h>
#include <bpf/libbpf.h>
#include <bpftool_helpers.h>
#include <test_progs.h>
#include <bpf/bpf.h>
#include "security_bpf_map.skel.h"

#define PROTECTED_MAP_NAME      "prot_map"
#define UNPROTECTED_MAP_NAME    "not_prot_map"
#define BPF_ITER_FILE           "bpf_iter_map_elem.bpf.o"
#define BPFFS_PIN_DIR           "/sys/fs/bpf/test_bpftool_map"
#define INNER_MAP_NAME          "inner_map_tt"
#define OUTER_MAP_NAME          "outer_map_tt"

#define MAP_NAME_MAX_LEN        64
#define PATH_MAX_LEN            128

enum map_protection {
        PROTECTED,
        UNPROTECTED
};

struct test_desc {
        char *name;
        enum map_protection protection;
        struct bpf_map *map;
        char *map_name;
        bool pinned;
        char pin_path[PATH_MAX_LEN];
        bool write_must_fail;
};

static struct security_bpf_map *general_setup(void)
{
        struct security_bpf_map *skel;
        uint32_t key, value;
        int ret, i;

        skel = security_bpf_map__open_and_load();
        if (!ASSERT_OK_PTR(skel, "open and load skeleton"))
                goto end;

        struct bpf_map *maps[] = {skel->maps.prot_map, skel->maps.not_prot_map};

        ret = security_bpf_map__attach(skel);
        if (!ASSERT_OK(ret, "attach maps security programs"))
                goto end_destroy;

        for (i = 0; i < sizeof(maps)/sizeof(struct bpf_map *); i++) {
                for (key = 0; key < 2; key++) {
                        int ret = bpf_map__update_elem(maps[i], &key,
                                        sizeof(key), &key, sizeof(key),
                                        0);
                        if (!ASSERT_OK(ret, "set initial map value"))
                                goto end_destroy;
                }
        }

        key = 0;
        value = 1;
        ret = bpf_map__update_elem(skel->maps.prot_status_map, &key,
                        sizeof(key), &value, sizeof(value), 0);
        if (!ASSERT_OK(ret, "configure map protection"))
                goto end_destroy;

        if (!ASSERT_OK(mkdir(BPFFS_PIN_DIR, S_IFDIR), "create bpffs pin dir"))
                goto end_destroy;

        return skel;
end_destroy:
        security_bpf_map__destroy(skel);
end:
        return NULL;
}

static void general_cleanup(struct security_bpf_map *skel)
{
        rmdir(BPFFS_PIN_DIR);
        security_bpf_map__destroy(skel);
}

static void update_test_desc(struct security_bpf_map *skel,
                              struct test_desc *test)
{
        /* Now that the skeleton is loaded, update all missing fields to
         * have the subtest properly configured
         */
        if (test->protection == PROTECTED) {
                test->map = skel->maps.prot_map;
                test->map_name = PROTECTED_MAP_NAME;
        } else {
                test->map = skel->maps.not_prot_map;
                test->map_name = UNPROTECTED_MAP_NAME;
        }
}

static int test_setup(struct security_bpf_map *skel, struct test_desc *desc)
{
        int ret;

        update_test_desc(skel, desc);

        if (desc->pinned) {
                ret = snprintf(desc->pin_path, PATH_MAX_LEN, "%s/%s", BPFFS_PIN_DIR,
                                desc->name);
                if (!ASSERT_GT(ret, 0, "format pin path"))
                        return 1;
                ret = bpf_map__pin(desc->map, desc->pin_path);
                if (!ASSERT_OK(ret, "pin map"))
                        return 1;
        }

        return 0;
}

static void test_cleanup(struct test_desc *desc)
{
        if (desc->pinned)
                bpf_map__unpin(desc->map, NULL);
}

static int lookup_map_value(char *map_handle)
{
        char cmd[MAX_BPFTOOL_CMD_LEN];
        int ret = 0;

        ret = snprintf(cmd, MAX_BPFTOOL_CMD_LEN, "map lookup %s key 0 0 0 0",
                        map_handle);
        if (!ASSERT_GT(ret, 0, "format map lookup cmd"))
                return 1;
        return run_bpftool_command(cmd);
}

static int read_map_btf_data(char *map_handle)
{
        char cmd[MAX_BPFTOOL_CMD_LEN];
        int ret = 0;

        ret = snprintf(cmd, MAX_BPFTOOL_CMD_LEN, "btf dump map %s",
                        map_handle);
        if (!ASSERT_GT(ret, 0, "format map btf dump cmd"))
                return 1;
        return run_bpftool_command(cmd);
}

static int write_map_value(char *map_handle)
{
        char cmd[MAX_BPFTOOL_CMD_LEN];
        int ret = 0;

        ret = snprintf(cmd, MAX_BPFTOOL_CMD_LEN,
                       "map update %s key 0 0 0 0 value 1 1 1 1", map_handle);
        if (!ASSERT_GT(ret, 0, "format value write cmd"))
                return 1;
        return run_bpftool_command(cmd);
}

static int delete_map_value(char *map_handle)
{
        char cmd[MAX_BPFTOOL_CMD_LEN];
        int ret = 0;

        ret = snprintf(cmd, MAX_BPFTOOL_CMD_LEN,
                       "map delete %s key 0 0 0 0", map_handle);
        if (!ASSERT_GT(ret, 0, "format value deletion cmd"))
                return 1;
        return run_bpftool_command(cmd);
}

static int iterate_on_map_values(char *map_handle, char *iter_pin_path)
{
        char cmd[MAX_BPFTOOL_CMD_LEN];
        int ret = 0;


        ret = snprintf(cmd, MAX_BPFTOOL_CMD_LEN, "iter pin %s %s map %s",
                       BPF_ITER_FILE, iter_pin_path, map_handle);
        if (!ASSERT_GT(ret, 0, "format iterator creation cmd"))
                return 1;
        ret = run_bpftool_command(cmd);
        if (ret)
                return ret;
        ret = snprintf(cmd, MAP_NAME_MAX_LEN, "cat %s", iter_pin_path);
        if (ret < 0)
                goto cleanup;
        ret = system(cmd);

cleanup:
        unlink(iter_pin_path);
        return ret;
}

static int create_inner_map(void)
{
        char cmd[MAX_BPFTOOL_CMD_LEN];
        int ret = 0;

        ret = snprintf(
                cmd, MAX_BPFTOOL_CMD_LEN,
                "map create %s/%s type array key 4 value 4 entries 4 name %s",
                BPFFS_PIN_DIR, INNER_MAP_NAME, INNER_MAP_NAME);
        if (!ASSERT_GT(ret, 0, "format inner map create cmd"))
                return 1;
        return run_bpftool_command(cmd);
}

static int create_outer_map(void)
{
        char cmd[MAX_BPFTOOL_CMD_LEN];
        int ret = 0;

        ret = snprintf(
                cmd, MAX_BPFTOOL_CMD_LEN,
                "map create %s/%s type hash_of_maps key 4 value 4 entries 2 name %s inner_map name %s",
                BPFFS_PIN_DIR, OUTER_MAP_NAME, OUTER_MAP_NAME, INNER_MAP_NAME);
        if (!ASSERT_GT(ret, 0, "format outer map create cmd"))
                return 1;
        return run_bpftool_command(cmd);
}

static void delete_pinned_map(char *map_name)
{
        char pin_path[PATH_MAX_LEN];
        int ret;

        ret = snprintf(pin_path, PATH_MAX_LEN, "%s/%s", BPFFS_PIN_DIR,
                       map_name);
        if (ret >= 0)
                unlink(pin_path);
}

static int add_outer_map_entry(int key)
{
        char cmd[MAX_BPFTOOL_CMD_LEN];
        int ret = 0;

        ret = snprintf(
                cmd, MAX_BPFTOOL_CMD_LEN,
                "map update pinned %s/%s key %d 0 0 0 value name %s",
                BPFFS_PIN_DIR, OUTER_MAP_NAME, key, INNER_MAP_NAME);
        if (!ASSERT_GT(ret, 0, "format outer map value addition cmd"))
                return 1;
        return run_bpftool_command(cmd);
}

static void test_basic_access(struct test_desc *desc)
{
        char map_handle[MAP_NAME_MAX_LEN];
        char iter_pin_path[PATH_MAX_LEN];
        int ret;

        if (desc->pinned)
                ret = snprintf(map_handle, MAP_NAME_MAX_LEN, "pinned %s",
                               desc->pin_path);
        else
                ret = snprintf(map_handle, MAP_NAME_MAX_LEN, "name %s",
                               desc->map_name);
        if (!ASSERT_GT(ret, 0, "format map handle"))
                return;

        ret = lookup_map_value(map_handle);
        ASSERT_OK(ret, "read map value");

        ret = read_map_btf_data(map_handle);
        ASSERT_OK(ret, "read map btf data");

        ret = write_map_value(map_handle);
        ASSERT_OK(desc->write_must_fail ? !ret : ret, "write map value");

        ret = delete_map_value(map_handle);
        ASSERT_OK(desc->write_must_fail ? !ret : ret, "delete map value");
        /* Restore deleted value */
        if (!ret)
                write_map_value(map_handle);

        ret = snprintf(iter_pin_path, PATH_MAX_LEN, "%s/iter", BPFFS_PIN_DIR);
        if (ASSERT_GT(ret, 0, "format iter pin path")) {
                ret = iterate_on_map_values(map_handle, iter_pin_path);
                ASSERT_OK(ret, "iterate on map values");
        }
}

static void test_create_nested_maps(void)
{
        if (!ASSERT_OK(create_inner_map(), "create inner map"))
                return;
        if (!ASSERT_OK(create_outer_map(), "create outer map"))
                goto end_cleanup_inner;
        ASSERT_OK(add_outer_map_entry(0), "add a first entry in outer map");
        ASSERT_OK(add_outer_map_entry(1), "add a second entry in outer map");
        ASSERT_NEQ(add_outer_map_entry(2), 0, "add a third entry in outer map");

        delete_pinned_map(OUTER_MAP_NAME);
end_cleanup_inner:
        delete_pinned_map(INNER_MAP_NAME);
}

static void test_btf_list(void)
{
        ASSERT_OK(run_bpftool_command("btf list"), "list btf data");
}

static struct test_desc tests[] = {
        {
                .name = "unprotected_unpinned",
                .protection = UNPROTECTED,
                .map_name = UNPROTECTED_MAP_NAME,
                .pinned = false,
                .write_must_fail = false,
        },
        {
                .name = "unprotected_pinned",
                .protection = UNPROTECTED,
                .map_name = UNPROTECTED_MAP_NAME,
                .pinned = true,
                .write_must_fail = false,
        },
        {
                .name = "protected_unpinned",
                .protection = PROTECTED,
                .map_name = UNPROTECTED_MAP_NAME,
                .pinned = false,
                .write_must_fail = true,
        },
        {
                .name = "protected_pinned",
                .protection = PROTECTED,
                .map_name = UNPROTECTED_MAP_NAME,
                .pinned = true,
                .write_must_fail = true,
        }
};

static const size_t tests_count = ARRAY_SIZE(tests);

void test_bpftool_maps_access(void)
{
        struct security_bpf_map *skel;
        struct test_desc *current;
        int i;

        skel = general_setup();
        if (!ASSERT_OK_PTR(skel, "prepare programs"))
                goto cleanup;

        for (i = 0; i < tests_count; i++) {
                current = &tests[i];
                if (!test__start_subtest(current->name))
                        continue;
                if (ASSERT_OK(test_setup(skel, current), "subtest setup")) {
                        test_basic_access(current);
                        test_cleanup(current);
                }
        }
        if (test__start_subtest("nested_maps"))
                test_create_nested_maps();
        if (test__start_subtest("btf_list"))
                test_btf_list();

cleanup:
        general_cleanup(skel);
}