blob: 4ffe4fe6b78e938f758046c1d8d89cbe60c7645e [file]
// SPDX-License-Identifier: GPL-2.0
#include <bpf/bpf.h>
#include <bpf/btf.h>
#include <test_progs.h>
#if defined(__x86_64__) || defined(__powerpc__) || defined(__aarch64__) || \
(defined(__riscv) && __riscv_xlen == 64)
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);
}
#define GOTOX_LOG_SZ (256 * 1024)
/* Load and return the fd, which the caller owns. */
static int gotox_prog_load_funcs_fd(struct bpf_insn *insns, __u32 insn_cnt,
int *fd_array, __u32 fd_array_cnt, char *log,
int btf_fd, struct bpf_func_info *fi, __u32 fi_cnt)
{
LIBBPF_OPTS(bpf_prog_load_opts, opts);
log[0] = 0;
opts.fd_array = fd_array;
opts.fd_array_cnt = fd_array_cnt;
opts.log_buf = log;
opts.log_size = GOTOX_LOG_SZ;
opts.log_level = 1;
if (fi_cnt) {
opts.prog_btf_fd = btf_fd;
opts.func_info = fi;
opts.func_info_cnt = fi_cnt;
opts.func_info_rec_size = sizeof(*fi);
}
return bpf_prog_load(BPF_PROG_TYPE_XDP, NULL, "GPL", insns, insn_cnt, &opts);
}
/* Load for the outcome only: 0 or the negative errno. */
static int gotox_prog_load_funcs(struct bpf_insn *insns, __u32 insn_cnt,
int *fd_array, __u32 fd_array_cnt, char *log,
int btf_fd, struct bpf_func_info *fi, __u32 fi_cnt)
{
int prog_fd;
prog_fd = gotox_prog_load_funcs_fd(insns, insn_cnt, fd_array, fd_array_cnt, log,
btf_fd, fi, fi_cnt);
if (prog_fd < 0)
return prog_fd;
close(prog_fd);
return 0;
}
static int gotox_prog_load(struct bpf_insn *insns, __u32 insn_cnt,
int *fd_array, __u32 fd_array_cnt, char *log)
{
return gotox_prog_load_funcs(insns, insn_cnt, fd_array, fd_array_cnt, log,
-1, NULL, 0);
}
static int gotox_jt_create_offs(const __u32 *offs, __u32 cnt)
{
struct bpf_insn_array_value val = {};
int map_fd;
__u32 i;
map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, cnt);
if (!ASSERT_GE(map_fd, 0, "map_create"))
return map_fd;
for (i = 0; i < cnt; i++) {
val.orig_off = offs[i];
if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &i, &val, 0), 0,
"bpf_map_update_elem"))
goto err;
}
if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
goto err;
return map_fd;
err:
close(map_fd);
return -1;
}
#define GOTOX_SUB_START 4
#define GOTOX_MAIN_TGT 2
#define GOTOX_SUB_TGT 8
#define GOTOX_TWO_INSN_CNT 10
static void gotox_two_subprogs_fill(struct bpf_insn *insns, __u32 jt_idx, __u32 jt_off)
{
insns[0] = BPF_MOV64_IMM(BPF_REG_0, 0);
insns[1] = BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 0,
GOTOX_SUB_START - 1 - 1);
insns[GOTOX_MAIN_TGT] = BPF_MOV64_IMM(BPF_REG_0, 0);
insns[3] = BPF_EXIT_INSN();
/* r1 = &jt[0], by index 'jt_idx' into fd_array */
insns[GOTOX_SUB_START] = (struct bpf_insn) {
.code = BPF_LD | BPF_DW | BPF_IMM,
.dst_reg = BPF_REG_1,
.src_reg = BPF_PSEUDO_MAP_IDX_VALUE,
.imm = jt_idx,
};
insns[GOTOX_SUB_START + 1] = (struct bpf_insn) { .imm = 0 };
insns[6] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, jt_off * 8);
insns[7] = BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0, 0);
insns[GOTOX_SUB_TGT] = BPF_MOV64_IMM(BPF_REG_0, 1);
insns[9] = BPF_EXIT_INSN();
}
/*
* Load 'insns' with the two jump tables 'jt_a' and 'jt_b', in this order in
* fd_array, and check the outcome. 'fi' describes the subprograms when given.
*/
static void check_gotox_two_maps(struct bpf_insn *insns, __u32 insn_cnt,
const __u32 *jt_a, __u32 a_cnt,
const __u32 *jt_b, __u32 b_cnt,
int btf_fd, struct bpf_func_info *fi, __u32 fi_cnt,
int expect_err, const char *expect_msg)
{
int map_fd[2] = { -1, -1 };
char *log;
int err;
log = calloc(1, GOTOX_LOG_SZ);
if (!ASSERT_OK_PTR(log, "calloc log"))
return;
map_fd[0] = gotox_jt_create_offs(jt_a, a_cnt);
if (map_fd[0] < 0)
goto free_log;
map_fd[1] = gotox_jt_create_offs(jt_b, b_cnt);
if (map_fd[1] < 0)
goto close_maps;
if (fi)
err = gotox_prog_load_funcs(insns, insn_cnt, map_fd, 2, log,
btf_fd, fi, fi_cnt);
else
err = gotox_prog_load(insns, insn_cnt, map_fd, 2, log);
if (expect_err)
ASSERT_EQ(err, expect_err, "program should have been rejected");
else
ASSERT_EQ(err, 0, "bpf(BPF_PROG_LOAD)");
if (expect_msg)
ASSERT_HAS_SUBSTR(log, expect_msg, "verifier log");
close_maps:
close(map_fd[0]);
if (map_fd[1] >= 0)
close(map_fd[1]);
free_log:
free(log);
}
static void check_gotox_target_other_subprog(void)
{
const __u32 jt_main[] = { GOTOX_MAIN_TGT };
const __u32 jt_sub[] = { GOTOX_SUB_TGT };
struct bpf_insn insns[GOTOX_TWO_INSN_CNT];
gotox_two_subprogs_fill(insns, 0, 0);
check_gotox_two_maps(insns, ARRAY_SIZE(insns), jt_main, ARRAY_SIZE(jt_main),
jt_sub, ARRAY_SIZE(jt_sub), -1, NULL, 0,
-EINVAL, "indirect jump from insn 7 to 2 leaves the subprog [4,10)");
}
static void check_gotox_jt_per_subprog(void)
{
const __u32 jt_main[] = { GOTOX_MAIN_TGT };
const __u32 jt_sub[] = { GOTOX_SUB_TGT };
struct bpf_insn insns[GOTOX_TWO_INSN_CNT];
gotox_two_subprogs_fill(insns, 1, 0);
check_gotox_two_maps(insns, ARRAY_SIZE(insns), jt_main, ARRAY_SIZE(jt_main),
jt_sub, ARRAY_SIZE(jt_sub), -1, NULL, 0,
0, NULL);
}
#define GOTOX_DEAD_MAIN_LEN 7
#define GOTOX_DEAD_SUB_LEN 6
#define GOTOX_DEAD_INSN_CNT (GOTOX_DEAD_MAIN_LEN + 3 * GOTOX_DEAD_SUB_LEN)
#define GOTOX_DEAD_LIVE_CNT 10
/* r1 = &jt[0], by index 'jt_idx' into fd_array; r1 = *r1; gotox r1; r0 = 0; exit */
static void gotox_sub_fill(struct bpf_insn *insns, __u32 start, __u32 jt_idx)
{
insns[start] = (struct bpf_insn) {
.code = BPF_LD | BPF_DW | BPF_IMM,
.dst_reg = BPF_REG_1,
.src_reg = BPF_PSEUDO_MAP_IDX_VALUE,
.imm = jt_idx,
};
insns[start + 1] = (struct bpf_insn) { .imm = 0 };
insns[start + 2] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, 0);
insns[start + 3] = BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0, 0);
insns[start + 4] = BPF_MOV64_IMM(BPF_REG_0, 0);
insns[start + 5] = BPF_EXIT_INSN();
}
/*
* Two subprograms which are only called from dead code sit in front of the
* live one, and all three own a jump table. Dead code removal drops both in
* one go, which compacts subprog_info[] along with the tables.
*/
static void check_gotox_jt_dead_subprogs(void)
{
const __u32 sub_start[3] = { GOTOX_DEAD_MAIN_LEN,
GOTOX_DEAD_MAIN_LEN + GOTOX_DEAD_SUB_LEN,
GOTOX_DEAD_MAIN_LEN + 2 * GOTOX_DEAD_SUB_LEN };
struct bpf_insn insns[GOTOX_DEAD_INSN_CNT];
int map_fd[3] = { -1, -1, -1 };
struct bpf_prog_info info = {};
__u32 len = sizeof(info), tgt, i;
int prog_fd;
char *log;
log = calloc(1, GOTOX_LOG_SZ);
if (!ASSERT_OK_PTR(log, "calloc log"))
return;
insns[0] = BPF_MOV64_IMM(BPF_REG_1, 0);
insns[1] = BPF_JMP_IMM(BPF_JEQ, BPF_REG_1, 0, 2);
insns[2] = BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 0,
sub_start[0] - 2 - 1);
insns[3] = BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 0,
sub_start[1] - 3 - 1);
insns[4] = BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 0,
sub_start[2] - 4 - 1);
insns[5] = BPF_MOV64_IMM(BPF_REG_0, 0);
insns[6] = BPF_EXIT_INSN();
for (i = 0; i < ARRAY_SIZE(sub_start); i++) {
gotox_sub_fill(insns, sub_start[i], i);
tgt = sub_start[i] + 4;
map_fd[i] = gotox_jt_create_offs(&tgt, 1);
if (map_fd[i] < 0)
goto close_maps;
}
prog_fd = gotox_prog_load_funcs_fd(insns, ARRAY_SIZE(insns), map_fd,
ARRAY_SIZE(map_fd), log, -1, NULL, 0);
if (!ASSERT_GE(prog_fd, 0, "bpf(BPF_PROG_LOAD)"))
goto close_maps;
/* Both dead subprograms and the calls to them must be gone. */
if (ASSERT_OK(bpf_prog_get_info_by_fd(prog_fd, &info, &len),
"bpf_prog_get_info_by_fd"))
ASSERT_EQ(info.xlated_prog_len / sizeof(struct bpf_insn),
GOTOX_DEAD_LIVE_CNT, "xlated insn count");
close(prog_fd);
close_maps:
for (i = 0; i < ARRAY_SIZE(map_fd); i++)
if (map_fd[i] >= 0)
close(map_fd[i]);
free(log);
}
#define GOTOX_FWD_GOTOX 11
#define GOTOX_FWD_OWN_TGT 12
#define GOTOX_FWD_SUB_START 14
#define GOTOX_FWD_INSN_CNT 16
static void gotox_from_main_fill(struct bpf_insn *insns)
{
insns[0] = BPF_MOV64_REG(BPF_REG_6, BPF_REG_1);
insns[1] = BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 0,
GOTOX_FWD_SUB_START - 1 - 1);
insns[2] = BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_6,
offsetof(struct xdp_md, ingress_ifindex));
insns[3] = BPF_JMP_IMM(BPF_JEQ, BPF_REG_2, 0, 4);
/* r1 = &jt_leaves[0], by index 1 into fd_array */
insns[4] = (struct bpf_insn) {
.code = BPF_LD | BPF_DW | BPF_IMM,
.dst_reg = BPF_REG_1,
.src_reg = BPF_PSEUDO_MAP_IDX_VALUE,
.imm = 1,
};
insns[5] = (struct bpf_insn) { .imm = 0 };
insns[6] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, 0);
insns[7] = BPF_JMP_A(3);
/* r1 = &jt_own[0], by index 0 into fd_array */
insns[8] = (struct bpf_insn) {
.code = BPF_LD | BPF_DW | BPF_IMM,
.dst_reg = BPF_REG_1,
.src_reg = BPF_PSEUDO_MAP_IDX_VALUE,
.imm = 0,
};
insns[9] = (struct bpf_insn) { .imm = 0 };
insns[10] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, 0);
insns[GOTOX_FWD_GOTOX] = BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0, 0);
insns[GOTOX_FWD_OWN_TGT] = BPF_MOV64_IMM(BPF_REG_0, 0);
insns[13] = BPF_EXIT_INSN();
insns[GOTOX_FWD_SUB_START] = BPF_MOV64_IMM(BPF_REG_0, 1);
insns[15] = BPF_EXIT_INSN();
}
static void check_gotox_target_subprog_from_main(void)
{
const __u32 jt_own[] = { GOTOX_FWD_OWN_TGT };
const __u32 jt_leaves[] = { GOTOX_FWD_SUB_START };
struct bpf_insn insns[GOTOX_FWD_INSN_CNT];
gotox_from_main_fill(insns);
check_gotox_two_maps(insns, ARRAY_SIZE(insns), jt_own, ARRAY_SIZE(jt_own),
jt_leaves, ARRAY_SIZE(jt_leaves), -1, NULL, 0,
-EINVAL, "indirect jump from insn 11 to 14 leaves the subprog [0,14)");
}
/*
* The only map of the subprog holding the gotox reaches past that subprog, so
* the subprog is left without a jump table at all.
*/
static void check_gotox_jt_spans_subprogs(void)
{
const __u32 jt_span[] = { GOTOX_FWD_OWN_TGT, GOTOX_FWD_SUB_START };
const __u32 jt_leaves[] = { GOTOX_FWD_SUB_START };
struct bpf_insn insns[GOTOX_FWD_INSN_CNT];
gotox_from_main_fill(insns);
check_gotox_two_maps(insns, ARRAY_SIZE(insns), jt_span, ARRAY_SIZE(jt_span),
jt_leaves, ARRAY_SIZE(jt_leaves), -1, NULL, 0,
-EINVAL, "jump table of subprog starting at 0 spans multiple subprogs");
}
/*
* The subprog holding the gotox has a well formed jump table of its own and
* also collects a map that reaches past its end. The spanning map is still
* rejected, even though the subprog is not left without a table.
*/
static void check_gotox_jt_spans_with_own_table(void)
{
const __u32 jt_own[] = { GOTOX_FWD_OWN_TGT };
const __u32 jt_span[] = { GOTOX_FWD_OWN_TGT, GOTOX_FWD_SUB_START };
struct bpf_insn insns[GOTOX_FWD_INSN_CNT];
gotox_from_main_fill(insns);
check_gotox_two_maps(insns, ARRAY_SIZE(insns), jt_own, ARRAY_SIZE(jt_own),
jt_span, ARRAY_SIZE(jt_span), -1, NULL, 0,
-EINVAL, "jump table of subprog starting at 0 spans multiple subprogs");
}
#define GOTOX_SLICE_SUB_START 6
#define GOTOX_SLICE_GOTOX 14
#define GOTOX_SLICE_SUB_TGT 15
#define GOTOX_SLICE_INSN_CNT 17
static void gotox_slice_fill(struct bpf_insn *insns)
{
insns[0] = BPF_MOV64_IMM(BPF_REG_0, 0);
insns[1] = BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 0,
GOTOX_SLICE_SUB_START - 1 - 1);
insns[2] = BPF_MOV64_IMM(BPF_REG_0, 0);
insns[3] = BPF_MOV64_IMM(BPF_REG_0, 0);
insns[4] = BPF_MOV64_IMM(BPF_REG_0, 0);
insns[5] = BPF_EXIT_INSN();
insns[GOTOX_SLICE_SUB_START] =
BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1,
offsetof(struct xdp_md, ingress_ifindex));
insns[7] = BPF_ALU64_IMM(BPF_AND, BPF_REG_2, 1);
insns[8] = BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, 1);
insns[9] = BPF_ALU64_IMM(BPF_LSH, BPF_REG_2, 3);
/* r1 = &jt_main[0], by index 0 into fd_array */
insns[10] = (struct bpf_insn) {
.code = BPF_LD | BPF_DW | BPF_IMM,
.dst_reg = BPF_REG_1,
.src_reg = BPF_PSEUDO_MAP_IDX_VALUE,
.imm = 0,
};
insns[11] = (struct bpf_insn) { .imm = 0 };
insns[12] = BPF_ALU64_REG(BPF_ADD, BPF_REG_1, BPF_REG_2);
insns[13] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, 0);
insns[GOTOX_SLICE_GOTOX] = BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0, 0);
insns[GOTOX_SLICE_SUB_TGT] = BPF_MOV64_IMM(BPF_REG_0, 1);
insns[16] = BPF_EXIT_INSN();
}
static void check_gotox_index_slice_other_subprog(void)
{
const __u32 jt_main[] = { 2, 3, 4 };
const __u32 jt_sub[] = { GOTOX_SLICE_SUB_TGT };
struct bpf_insn insns[GOTOX_SLICE_INSN_CNT];
gotox_slice_fill(insns);
check_gotox_two_maps(insns, ARRAY_SIZE(insns), jt_main, ARRAY_SIZE(jt_main),
jt_sub, ARRAY_SIZE(jt_sub), -1, NULL, 0,
-EINVAL, "indirect jump from insn 14 to 3 leaves the subprog [6,17)");
}
static int gotox_btf_create(const __u32 *starts, const __u8 *linkage, __u32 cnt,
struct bpf_func_info *fi, struct btf **pbtf)
{
int int_id, proto_id, id;
struct btf *btf;
char name[24];
__u32 i;
btf = btf__new_empty();
if (!ASSERT_OK_PTR(btf, "btf__new_empty"))
return -1;
int_id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
if (!ASSERT_GT(int_id, 0, "btf__add_int"))
goto err;
proto_id = btf__add_func_proto(btf, int_id);
if (!ASSERT_GT(proto_id, 0, "btf__add_func_proto"))
goto err;
for (i = 0; i < cnt; i++) {
snprintf(name, sizeof(name), "gotox_f%u", i);
id = btf__add_func(btf, name, linkage[i], proto_id);
if (!ASSERT_GT(id, 0, "btf__add_func"))
goto err;
fi[i].insn_off = starts[i];
fi[i].type_id = id;
}
if (!ASSERT_OK(btf__load_into_kernel(btf), "btf__load_into_kernel"))
goto err;
*pbtf = btf;
return btf__fd(btf);
err:
btf__free(btf);
return -1;
}
static void check_gotox_target_other_global_subprog(void)
{
const __u32 starts[] = { 0, GOTOX_SUB_START };
const __u8 linkage[] = { BTF_FUNC_GLOBAL, BTF_FUNC_GLOBAL };
const __u32 jt_main[] = { GOTOX_MAIN_TGT };
const __u32 jt_sub[] = { GOTOX_SUB_TGT };
struct bpf_insn insns[GOTOX_TWO_INSN_CNT];
struct bpf_func_info fi[2];
struct btf *btf = NULL;
int btf_fd;
gotox_two_subprogs_fill(insns, 0, 0);
btf_fd = gotox_btf_create(starts, linkage, ARRAY_SIZE(starts), fi, &btf);
if (btf_fd < 0)
return;
check_gotox_two_maps(insns, ARRAY_SIZE(insns), jt_main, ARRAY_SIZE(jt_main),
jt_sub, ARRAY_SIZE(jt_sub), btf_fd, fi, ARRAY_SIZE(fi),
-EINVAL, "indirect jump from insn 7 to 2 leaves the subprog [4,10)");
btf__free(btf);
}
#define GOTOX_CB_MAIN_TGT 6
#define GOTOX_CB_START 8
#define GOTOX_CB_GOTOX 11
#define GOTOX_CB_TGT 12
#define GOTOX_CB_INSN_CNT 14
static void gotox_callback_fill(struct bpf_insn *insns)
{
insns[0] = BPF_MOV64_IMM(BPF_REG_1, 1);
/* r2 = &callback */
insns[1] = (struct bpf_insn) {
.code = BPF_LD | BPF_DW | BPF_IMM,
.dst_reg = BPF_REG_2,
.src_reg = BPF_PSEUDO_FUNC,
.imm = GOTOX_CB_START - 1 - 1,
};
insns[2] = (struct bpf_insn) { .imm = 0 };
insns[3] = BPF_MOV64_IMM(BPF_REG_3, 0);
insns[4] = BPF_MOV64_IMM(BPF_REG_4, 0);
insns[5] = BPF_EMIT_CALL(BPF_FUNC_loop);
insns[GOTOX_CB_MAIN_TGT] = BPF_MOV64_IMM(BPF_REG_0, 0);
insns[7] = BPF_EXIT_INSN();
/* r1 = &jt_main[0], by index 0 into fd_array */
insns[GOTOX_CB_START] = (struct bpf_insn) {
.code = BPF_LD | BPF_DW | BPF_IMM,
.dst_reg = BPF_REG_1,
.src_reg = BPF_PSEUDO_MAP_IDX_VALUE,
.imm = 0,
};
insns[9] = (struct bpf_insn) { .imm = 0 };
insns[10] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, 0);
insns[GOTOX_CB_GOTOX] = BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0, 0);
insns[GOTOX_CB_TGT] = BPF_MOV64_IMM(BPF_REG_0, 0);
insns[13] = BPF_EXIT_INSN();
}
static void check_gotox_callback_leaves_subprog(void)
{
const __u32 starts[] = { 0, GOTOX_CB_START };
const __u8 linkage[] = { BTF_FUNC_GLOBAL, BTF_FUNC_STATIC };
const __u32 jt_main[] = { GOTOX_CB_MAIN_TGT };
const __u32 jt_cb[] = { GOTOX_CB_TGT };
struct bpf_insn insns[GOTOX_CB_INSN_CNT];
struct bpf_func_info fi[2];
struct btf *btf = NULL;
int btf_fd;
gotox_callback_fill(insns);
btf_fd = gotox_btf_create(starts, linkage, ARRAY_SIZE(starts), fi, &btf);
if (btf_fd < 0)
return;
check_gotox_two_maps(insns, ARRAY_SIZE(insns), jt_main, ARRAY_SIZE(jt_main),
jt_cb, ARRAY_SIZE(jt_cb), btf_fd, fi, ARRAY_SIZE(fi),
-EINVAL, "indirect jump from insn 11 to 6 leaves the subprog [8,14)");
btf__free(btf);
}
static void check_bpf_side(void)
{
check_bpf_no_lookup();
}
/* Return the xlated index of the first insn equal to 'insn', or -1. */
static int xlated_insn_index(int prog_fd, const struct bpf_insn *insn)
{
struct bpf_insn *xlated;
int idx = -1;
__u32 cnt, i;
if (!ASSERT_OK(get_xlated_program(prog_fd, &xlated, &cnt), "get_xlated_program"))
return -1;
for (i = 0; i < cnt; i++)
if (!memcmp(&xlated[i], insn, sizeof(*insn))) {
idx = i;
break;
}
free(xlated);
return idx;
}
/*
* Load 'insns' with a jump table naming insn 0, which a prologue prepend
* pushes down, and check that the slot followed it.
*/
static void check_gotox_target_prepend(enum bpf_prog_type prog_type,
struct bpf_insn *insns, __u32 insn_cnt)
{
LIBBPF_OPTS(bpf_prog_load_opts, opts);
struct bpf_insn_array_value val = {};
int prog_fd = -1, map_fd, idx;
__u32 key = 0;
map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, 1);
if (!ASSERT_GE(map_fd, 0, "map_create"))
return;
val.orig_off = 0;
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;
opts.fd_array = &map_fd;
opts.fd_array_cnt = 1;
prog_fd = bpf_prog_load(prog_type, NULL, "GPL", insns, insn_cnt, &opts);
if (!ASSERT_GE(prog_fd, 0, "bpf(BPF_PROG_LOAD)"))
goto cleanup;
idx = xlated_insn_index(prog_fd, &insns[0]);
if (!ASSERT_GT(idx, 0, "insn 0 was prepended to"))
goto cleanup;
if (!ASSERT_EQ(bpf_map_lookup_elem(map_fd, &key, &val), 0, "bpf_map_lookup_elem"))
goto cleanup;
ASSERT_EQ(val.xlated_off, (__u32)idx, "gotox target followed insn 0 past the prepend");
cleanup:
if (prog_fd >= 0)
close(prog_fd);
close(map_fd);
}
static void check_gotox_target_prologue_shift(void)
{
struct bpf_insn insns[] = {
/* insn 0: gotox target and subprog start */
BPF_MOV64_IMM(BPF_REG_0, 0),
/* may_goto +4 -> exit block, bounds the loop */
BPF_RAW_INSN(BPF_JMP | BPF_JCOND, 0, 0, 4, 0),
/* r1 = &jt[0] (insns 2 and 3) */
BPF_LD_IMM64_RAW(BPF_REG_1, BPF_PSEUDO_MAP_IDX_VALUE, 0),
BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, 0), /* r1 = ips[0] */
/* insn 5: gotox r1 -> insn 0 */
BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0, 0),
BPF_MOV64_IMM(BPF_REG_0, XDP_PASS), /* insn 6: exit block */
BPF_EXIT_INSN(),
};
check_gotox_target_prepend(BPF_PROG_TYPE_XDP, insns, ARRAY_SIZE(insns));
}
static void check_gotox_target_ctx_prologue_shift(void)
{
struct bpf_insn insns[] = {
/* insn 0: gotox target and subprog start */
BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_10, -8),
BPF_JMP_IMM(BPF_JEQ, BPF_REG_7, 0x5a5a, 12), /* second visit exits */
BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0x5a5a),
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), /* r6 = ctx */
BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_6, offsetof(struct __sk_buff, data)),
BPF_LDX_MEM(BPF_W, BPF_REG_3, BPF_REG_6, offsetof(struct __sk_buff, data_end)),
BPF_MOV64_REG(BPF_REG_4, BPF_REG_2),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 1),
BPF_JMP_REG(BPF_JGT, BPF_REG_4, BPF_REG_3, 5),
/* insn 9: direct packet write -> tc unclone prologue prepended at insn 0 */
BPF_ST_MEM(BPF_B, BPF_REG_2, 0, 0),
/* r5 = &jt[0] (insns 10 and 11) */
BPF_LD_IMM64_RAW(BPF_REG_5, BPF_PSEUDO_MAP_IDX_VALUE, 0),
BPF_LDX_MEM(BPF_DW, BPF_REG_5, BPF_REG_5, 0), /* r5 = ips[0] */
/* insn 13: gotox r5 -> insn 0 */
BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_5, 0, 0, 0),
BPF_MOV64_IMM(BPF_REG_0, 0), /* insn 14: exit block */
BPF_EXIT_INSN(),
};
check_gotox_target_prepend(BPF_PROG_TYPE_SCHED_CLS, insns, ARRAY_SIZE(insns));
}
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();
if (test__start_subtest("gotox-jt-spans-subprogs"))
check_gotox_jt_spans_subprogs();
if (test__start_subtest("gotox-jt-spans-with-own-table"))
check_gotox_jt_spans_with_own_table();
if (test__start_subtest("gotox-target-other-subprog"))
check_gotox_target_other_subprog();
if (test__start_subtest("gotox-jt-per-subprog"))
check_gotox_jt_per_subprog();
if (test__start_subtest("gotox-jt-dead-subprogs"))
check_gotox_jt_dead_subprogs();
if (test__start_subtest("gotox-target-subprog-from-main"))
check_gotox_target_subprog_from_main();
if (test__start_subtest("gotox-index-slice-other-subprog"))
check_gotox_index_slice_other_subprog();
if (test__start_subtest("gotox-target-other-global-subprog"))
check_gotox_target_other_global_subprog();
if (test__start_subtest("gotox-callback-leaves-subprog"))
check_gotox_callback_leaves_subprog();
if (test__start_subtest("gotox-target-prologue-shift"))
check_gotox_target_prologue_shift();
if (test__start_subtest("gotox-target-ctx-prologue-shift"))
check_gotox_target_ctx_prologue_shift();
}
#else
static void __test_bpf_insn_array(void)
{
test__skip();
}
#endif
void test_bpf_insn_array(void)
{
__test_bpf_insn_array();
}