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

#include <bpf/bpf.h>
#include <test_progs.h>

#if defined(__x86_64__) || defined(__powerpc__) || defined(__aarch64__)
static int map_create(__u32 map_type, __u32 max_entries)
{
        const char *map_name = "insn_array";
        __u32 key_size = 4;
        __u32 value_size = sizeof(struct bpf_insn_array_value);

        return bpf_map_create(map_type, map_name, key_size, value_size, max_entries, NULL);
}

static int prog_load(struct bpf_insn *insns, __u32 insn_cnt, int *fd_array, __u32 fd_array_cnt)
{
        LIBBPF_OPTS(bpf_prog_load_opts, opts);

        opts.fd_array = fd_array;
        opts.fd_array_cnt = fd_array_cnt;

        return bpf_prog_load(BPF_PROG_TYPE_XDP, NULL, "GPL", insns, insn_cnt, &opts);
}

static void __check_success(struct bpf_insn *insns, __u32 insn_cnt, __u32 *map_in, __u32 *map_out)
{
        struct bpf_insn_array_value val = {};
        int prog_fd = -1, map_fd, i;

        map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, insn_cnt);
        if (!ASSERT_GE(map_fd, 0, "map_create"))
                return;

        for (i = 0; i < insn_cnt; i++) {
                val.orig_off = map_in[i];
                if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &i, &val, 0), 0, "bpf_map_update_elem"))
                        goto cleanup;
        }

        if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
                goto cleanup;

        prog_fd = prog_load(insns, insn_cnt, &map_fd, 1);
        if (!ASSERT_GE(prog_fd, 0, "bpf(BPF_PROG_LOAD)"))
                goto cleanup;

        for (i = 0; i < insn_cnt; i++) {
                char buf[64];

                if (!ASSERT_EQ(bpf_map_lookup_elem(map_fd, &i, &val), 0, "bpf_map_lookup_elem"))
                        goto cleanup;

                snprintf(buf, sizeof(buf), "val.xlated_off should be equal map_out[%d]", i);
                ASSERT_EQ(val.xlated_off, map_out[i], buf);
        }

cleanup:
        close(prog_fd);
        close(map_fd);
}

/*
 * Load a program, which will not be anyhow mangled by the verifier.  Add an
 * insn_array map pointing to every instruction. Check that it hasn't changed
 * after the program load.
 */
static void check_one_to_one_mapping(void)
{
        struct bpf_insn insns[] = {
                BPF_MOV64_IMM(BPF_REG_0, 4),
                BPF_MOV64_IMM(BPF_REG_0, 3),
                BPF_MOV64_IMM(BPF_REG_0, 2),
                BPF_MOV64_IMM(BPF_REG_0, 1),
                BPF_MOV64_IMM(BPF_REG_0, 0),
                BPF_EXIT_INSN(),
        };
        __u32 map_in[] = {0, 1, 2, 3, 4, 5};
        __u32 map_out[] = {0, 1, 2, 3, 4, 5};

        __check_success(insns, ARRAY_SIZE(insns), map_in, map_out);
}

/*
 * Load a program with two patches (get jiffies, for simplicity). Add an
 * insn_array map pointing to every instruction. Check how it was changed
 * after the program load.
 */
static void check_simple(void)
{
        struct bpf_insn insns[] = {
                BPF_MOV64_IMM(BPF_REG_0, 2),
                BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_jiffies64),
                BPF_MOV64_IMM(BPF_REG_0, 1),
                BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_jiffies64),
                BPF_MOV64_IMM(BPF_REG_0, 0),
                BPF_EXIT_INSN(),
        };
        __u32 map_in[] = {0, 1, 2, 3, 4, 5};
        __u32 map_out[] = {0, 1, 4, 5, 8, 9};

        __check_success(insns, ARRAY_SIZE(insns), map_in, map_out);
}

/*
 * Verifier can delete code in two cases: nops & dead code. From insn
 * array's point of view, the two cases are the same, so test using
 * the simplest method: by loading some nops
 */
static void check_deletions(void)
{
        struct bpf_insn insns[] = {
                BPF_MOV64_IMM(BPF_REG_0, 2),
                BPF_JMP_IMM(BPF_JA, 0, 0, 0), /* nop */
                BPF_MOV64_IMM(BPF_REG_0, 1),
                BPF_JMP_IMM(BPF_JA, 0, 0, 0), /* nop */
                BPF_MOV64_IMM(BPF_REG_0, 0),
                BPF_EXIT_INSN(),
        };
        __u32 map_in[] = {0, 1, 2, 3, 4, 5};
        __u32 map_out[] = {0, -1, 1, -1, 2, 3};

        __check_success(insns, ARRAY_SIZE(insns), map_in, map_out);
}

/*
 * Same test as check_deletions, but also add code which adds instructions
 */
static void check_deletions_with_functions(void)
{
        struct bpf_insn insns[] = {
                BPF_JMP_IMM(BPF_JA, 0, 0, 0), /* nop */
                BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_jiffies64),
                BPF_JMP_IMM(BPF_JA, 0, 0, 0), /* nop */
                BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 1, 0, 2),
                BPF_MOV64_IMM(BPF_REG_0, 1),
                BPF_EXIT_INSN(),
                BPF_JMP_IMM(BPF_JA, 0, 0, 0), /* nop */
                BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_jiffies64),
                BPF_JMP_IMM(BPF_JA, 0, 0, 0), /* nop */
                BPF_MOV64_IMM(BPF_REG_0, 2),
                BPF_EXIT_INSN(),
        };
        __u32 map_in[] =  { 0, 1,  2, 3, 4, 5, /* func */  6, 7,  8, 9, 10};
        __u32 map_out[] = {-1, 0, -1, 3, 4, 5, /* func */ -1, 6, -1, 9, 10};

        __check_success(insns, ARRAY_SIZE(insns), map_in, map_out);
}

/*
 * Try to load a program with a map which points to outside of the program
 */
static void check_out_of_bounds_index(void)
{
        struct bpf_insn insns[] = {
                BPF_MOV64_IMM(BPF_REG_0, 4),
                BPF_MOV64_IMM(BPF_REG_0, 3),
                BPF_MOV64_IMM(BPF_REG_0, 2),
                BPF_MOV64_IMM(BPF_REG_0, 1),
                BPF_MOV64_IMM(BPF_REG_0, 0),
                BPF_EXIT_INSN(),
        };
        int prog_fd, map_fd;
        struct bpf_insn_array_value val = {};
        int key;

        map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, 1);
        if (!ASSERT_GE(map_fd, 0, "map_create"))
                return;

        key = 0;
        val.orig_off = ARRAY_SIZE(insns); /* too big */
        if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &key, &val, 0), 0, "bpf_map_update_elem"))
                goto cleanup;

        if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
                goto cleanup;

        prog_fd = prog_load(insns, ARRAY_SIZE(insns), &map_fd, 1);
        if (!ASSERT_EQ(prog_fd, -EINVAL, "program should have been rejected (prog_fd != -EINVAL)")) {
                close(prog_fd);
                goto cleanup;
        }

cleanup:
        close(map_fd);
}

/*
 * Try to load a program with a map which points to the middle of 16-bit insn
 */
static void check_mid_insn_index(void)
{
        struct bpf_insn insns[] = {
                BPF_LD_IMM64(BPF_REG_0, 0), /* 2 x 8 */
                BPF_EXIT_INSN(),
        };
        int prog_fd, map_fd;
        struct bpf_insn_array_value val = {};
        int key;

        map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, 1);
        if (!ASSERT_GE(map_fd, 0, "map_create"))
                return;

        key = 0;
        val.orig_off = 1; /* middle of 16-byte instruction */
        if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &key, &val, 0), 0, "bpf_map_update_elem"))
                goto cleanup;

        if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
                goto cleanup;

        prog_fd = prog_load(insns, ARRAY_SIZE(insns), &map_fd, 1);
        if (!ASSERT_EQ(prog_fd, -EINVAL, "program should have been rejected (prog_fd != -EINVAL)")) {
                close(prog_fd);
                goto cleanup;
        }

cleanup:
        close(map_fd);
}

static void check_incorrect_index(void)
{
        check_out_of_bounds_index();
        check_mid_insn_index();
}

static int set_bpf_jit_harden(char *level)
{
        char old_level;
        int err = -1;
        int fd = -1;

        fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK);
        if (fd < 0) {
                ASSERT_FAIL("open .../bpf_jit_harden returned %d (errno=%d)", fd, errno);
                return -1;
        }

        err = read(fd, &old_level, 1);
        if (err != 1) {
                ASSERT_FAIL("read from .../bpf_jit_harden returned %d (errno=%d)", err, errno);
                err = -1;
                goto end;
        }

        lseek(fd, 0, SEEK_SET);

        err = write(fd, level, 1);
        if (err != 1) {
                ASSERT_FAIL("write to .../bpf_jit_harden returned %d (errno=%d)", err, errno);
                err = -1;
                goto end;
        }

        err = 0;
        *level = old_level;
end:
        if (fd >= 0)
                close(fd);
        return err;
}

static void check_blindness(void)
{
        struct bpf_insn insns[] = {
                BPF_MOV64_IMM(BPF_REG_0, 4),
                BPF_MOV64_IMM(BPF_REG_0, 3),
                BPF_MOV64_IMM(BPF_REG_0, 2),
                BPF_MOV64_IMM(BPF_REG_0, 1),
                BPF_EXIT_INSN(),
        };
        int prog_fd = -1, map_fd;
        struct bpf_insn_array_value val = {};
        char bpf_jit_harden = '@'; /* non-exizsting value */
        int i;

        map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, ARRAY_SIZE(insns));
        if (!ASSERT_GE(map_fd, 0, "map_create"))
                return;

        for (i = 0; i < ARRAY_SIZE(insns); i++) {
                val.orig_off = i;
                if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &i, &val, 0), 0, "bpf_map_update_elem"))
                        goto cleanup;
        }

        if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
                goto cleanup;

        bpf_jit_harden = '2';
        if (set_bpf_jit_harden(&bpf_jit_harden)) {
                bpf_jit_harden = '@'; /* open, read or write failed => no write was done */
                goto cleanup;
        }

        prog_fd = prog_load(insns, ARRAY_SIZE(insns), &map_fd, 1);
        if (!ASSERT_GE(prog_fd, 0, "bpf(BPF_PROG_LOAD)"))
                goto cleanup;

        for (i = 0; i < ARRAY_SIZE(insns); i++) {
                char fmt[32];

                if (!ASSERT_EQ(bpf_map_lookup_elem(map_fd, &i, &val), 0, "bpf_map_lookup_elem"))
                        goto cleanup;

                snprintf(fmt, sizeof(fmt), "val should be equal 3*%d", i);
                ASSERT_EQ(val.xlated_off, i * 3, fmt);
        }

cleanup:
        /* restore the old one */
        if (bpf_jit_harden != '@')
                set_bpf_jit_harden(&bpf_jit_harden);

        close(prog_fd);
        close(map_fd);
}

/* Once map was initialized, it should be frozen */
static void check_load_unfrozen_map(void)
{
        struct bpf_insn insns[] = {
                BPF_MOV64_IMM(BPF_REG_0, 0),
                BPF_EXIT_INSN(),
        };
        int prog_fd = -1, map_fd;
        struct bpf_insn_array_value val = {};
        int i;

        map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, ARRAY_SIZE(insns));
        if (!ASSERT_GE(map_fd, 0, "map_create"))
                return;

        for (i = 0; i < ARRAY_SIZE(insns); i++) {
                val.orig_off = i;
                if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &i, &val, 0), 0, "bpf_map_update_elem"))
                        goto cleanup;
        }

        prog_fd = prog_load(insns, ARRAY_SIZE(insns), &map_fd, 1);
        if (!ASSERT_EQ(prog_fd, -EINVAL, "program should have been rejected (prog_fd != -EINVAL)"))
                goto cleanup;

        /* correctness: now freeze the map, the program should load fine */

        if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
                goto cleanup;

        prog_fd = prog_load(insns, ARRAY_SIZE(insns), &map_fd, 1);
        if (!ASSERT_GE(prog_fd, 0, "bpf(BPF_PROG_LOAD)"))
                goto cleanup;

        for (i = 0; i < ARRAY_SIZE(insns); i++) {
                if (!ASSERT_EQ(bpf_map_lookup_elem(map_fd, &i, &val), 0, "bpf_map_lookup_elem"))
                        goto cleanup;

                ASSERT_EQ(val.xlated_off, i, "val should be equal i");
        }

cleanup:
        close(prog_fd);
        close(map_fd);
}

/* Map can be used only by one BPF program */
static void check_no_map_reuse(void)
{
        struct bpf_insn insns[] = {
                BPF_MOV64_IMM(BPF_REG_0, 0),
                BPF_EXIT_INSN(),
        };
        int prog_fd = -1, map_fd, extra_fd = -1;
        struct bpf_insn_array_value val = {};
        int i;

        map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, ARRAY_SIZE(insns));
        if (!ASSERT_GE(map_fd, 0, "map_create"))
                return;

        for (i = 0; i < ARRAY_SIZE(insns); i++) {
                val.orig_off = i;
                if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &i, &val, 0), 0, "bpf_map_update_elem"))
                        goto cleanup;
        }

        if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
                goto cleanup;

        prog_fd = prog_load(insns, ARRAY_SIZE(insns), &map_fd, 1);
        if (!ASSERT_GE(prog_fd, 0, "bpf(BPF_PROG_LOAD)"))
                goto cleanup;

        for (i = 0; i < ARRAY_SIZE(insns); i++) {
                if (!ASSERT_EQ(bpf_map_lookup_elem(map_fd, &i, &val), 0, "bpf_map_lookup_elem"))
                        goto cleanup;

                ASSERT_EQ(val.xlated_off, i, "val should be equal i");
        }

        extra_fd = prog_load(insns, ARRAY_SIZE(insns), &map_fd, 1);
        if (!ASSERT_EQ(extra_fd, -EBUSY, "program should have been rejected (extra_fd != -EBUSY)"))
                goto cleanup;

        /* correctness: check that prog is still loadable without fd_array */
        extra_fd = prog_load(insns, ARRAY_SIZE(insns), NULL, 0);
        if (!ASSERT_GE(extra_fd, 0, "bpf(BPF_PROG_LOAD): expected no error"))
                goto cleanup;

cleanup:
        close(extra_fd);
        close(prog_fd);
        close(map_fd);
}

static void check_bpf_no_lookup(void)
{
        struct bpf_insn insns[] = {
                BPF_LD_MAP_FD(BPF_REG_1, 0),
                BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),
                BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
                BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8),
                BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
                BPF_EXIT_INSN(),
        };
        int prog_fd = -1, map_fd;

        map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, 1);
        if (!ASSERT_GE(map_fd, 0, "map_create"))
                return;

        insns[0].imm = map_fd;

        if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
                goto cleanup;

        prog_fd = prog_load(insns, ARRAY_SIZE(insns), NULL, 0);
        if (!ASSERT_EQ(prog_fd, -EINVAL, "program should have been rejected (prog_fd != -EINVAL)"))
                goto cleanup;

        /* correctness: check that prog is still loadable with normal map */
        close(map_fd);
        map_fd = map_create(BPF_MAP_TYPE_ARRAY, 1);
        insns[0].imm = map_fd;
        prog_fd = prog_load(insns, ARRAY_SIZE(insns), NULL, 0);
        if (!ASSERT_GE(prog_fd, 0, "bpf(BPF_PROG_LOAD)"))
                goto cleanup;

cleanup:
        close(prog_fd);
        close(map_fd);
}

static void check_bpf_side(void)
{
        check_bpf_no_lookup();
}

static void __test_bpf_insn_array(void)
{
        /* Test if offsets are adjusted properly */

        if (test__start_subtest("one2one"))
                check_one_to_one_mapping();

        if (test__start_subtest("simple"))
                check_simple();

        if (test__start_subtest("deletions"))
                check_deletions();

        if (test__start_subtest("deletions-with-functions"))
                check_deletions_with_functions();

        if (test__start_subtest("blindness"))
                check_blindness();

        /* Check all kinds of operations and related restrictions */

        if (test__start_subtest("incorrect-index"))
                check_incorrect_index();

        if (test__start_subtest("load-unfrozen-map"))
                check_load_unfrozen_map();

        if (test__start_subtest("no-map-reuse"))
                check_no_map_reuse();

        if (test__start_subtest("bpf-side-ops"))
                check_bpf_side();
}
#else
static void __test_bpf_insn_array(void)
{
        test__skip();
}
#endif

void test_bpf_insn_array(void)
{
        __test_bpf_insn_array();
}