mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-05-24 10:39:52 +00:00
selftests/bpf: specify expected instructions in test_verifier tests
Allows to specify expected and unexpected instruction sequences in test_verifier test cases. The instructions are requested from kernel after BPF program loading, thus allowing to check some of the transformations applied by BPF verifier. - `expected_insn` field specifies a sequence of instructions expected to be found in the program; - `unexpected_insn` field specifies a sequence of instructions that are not expected to be found in the program; - `INSN_OFF_MASK` and `INSN_IMM_MASK` values could be used to mask `off` and `imm` fields. - `SKIP_INSNS` could be used to specify that some instructions in the (un)expected pattern are not important (behavior similar to usage of `\t` in `errstr` field). The intended usage is as follows: { "inline simple bpf_loop call", .insns = { /* main */ BPF_ALU64_IMM(BPF_MOV, BPF_REG_1, 1), BPF_RAW_INSN(BPF_LD | BPF_IMM | BPF_DW, BPF_REG_2, BPF_PSEUDO_FUNC, 0, 6), ... BPF_EXIT_INSN(), /* callback */ BPF_ALU64_IMM(BPF_MOV, BPF_REG_0, 1), BPF_EXIT_INSN(), }, .expected_insns = { BPF_ALU64_IMM(BPF_MOV, BPF_REG_1, 1), SKIP_INSNS(), BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 8, 1) }, .unexpected_insns = { BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, INSN_OFF_MASK, INSN_IMM_MASK), }, .prog_type = BPF_PROG_TYPE_TRACEPOINT, .result = ACCEPT, .runs = 0, }, Here it is expected that move of 1 to register 1 would remain in place and helper function call instruction would be replaced by a relative call instruction. Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> Acked-by: Song Liu <songliubraving@fb.com> Link: https://lore.kernel.org/r/20220620235344.569325-2-eddyz87@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
parent
aca80dd95e
commit
933ff53191
1 changed files with 234 additions and 0 deletions
|
@ -51,6 +51,8 @@
|
|||
#endif
|
||||
|
||||
#define MAX_INSNS BPF_MAXINSNS
|
||||
#define MAX_EXPECTED_INSNS 32
|
||||
#define MAX_UNEXPECTED_INSNS 32
|
||||
#define MAX_TEST_INSNS 1000000
|
||||
#define MAX_FIXUPS 8
|
||||
#define MAX_NR_MAPS 23
|
||||
|
@ -58,6 +60,10 @@
|
|||
#define POINTER_VALUE 0xcafe4all
|
||||
#define TEST_DATA_LEN 64
|
||||
|
||||
#define INSN_OFF_MASK ((__s16)0xFFFF)
|
||||
#define INSN_IMM_MASK ((__s32)0xFFFFFFFF)
|
||||
#define SKIP_INSNS() BPF_RAW_INSN(0xde, 0xa, 0xd, 0xbeef, 0xdeadbeef)
|
||||
|
||||
#define F_NEEDS_EFFICIENT_UNALIGNED_ACCESS (1 << 0)
|
||||
#define F_LOAD_WITH_STRICT_ALIGNMENT (1 << 1)
|
||||
|
||||
|
@ -79,6 +85,23 @@ struct bpf_test {
|
|||
const char *descr;
|
||||
struct bpf_insn insns[MAX_INSNS];
|
||||
struct bpf_insn *fill_insns;
|
||||
/* If specified, test engine looks for this sequence of
|
||||
* instructions in the BPF program after loading. Allows to
|
||||
* test rewrites applied by verifier. Use values
|
||||
* INSN_OFF_MASK and INSN_IMM_MASK to mask `off` and `imm`
|
||||
* fields if content does not matter. The test case fails if
|
||||
* specified instructions are not found.
|
||||
*
|
||||
* The sequence could be split into sub-sequences by adding
|
||||
* SKIP_INSNS instruction at the end of each sub-sequence. In
|
||||
* such case sub-sequences are searched for one after another.
|
||||
*/
|
||||
struct bpf_insn expected_insns[MAX_EXPECTED_INSNS];
|
||||
/* If specified, test engine applies same pattern matching
|
||||
* logic as for `expected_insns`. If the specified pattern is
|
||||
* matched test case is marked as failed.
|
||||
*/
|
||||
struct bpf_insn unexpected_insns[MAX_UNEXPECTED_INSNS];
|
||||
int fixup_map_hash_8b[MAX_FIXUPS];
|
||||
int fixup_map_hash_48b[MAX_FIXUPS];
|
||||
int fixup_map_hash_16b[MAX_FIXUPS];
|
||||
|
@ -1126,6 +1149,214 @@ static bool cmp_str_seq(const char *log, const char *exp)
|
|||
return true;
|
||||
}
|
||||
|
||||
static int get_xlated_program(int fd_prog, struct bpf_insn **buf, int *cnt)
|
||||
{
|
||||
struct bpf_prog_info info = {};
|
||||
__u32 info_len = sizeof(info);
|
||||
__u32 xlated_prog_len;
|
||||
__u32 buf_element_size = sizeof(struct bpf_insn);
|
||||
|
||||
if (bpf_obj_get_info_by_fd(fd_prog, &info, &info_len)) {
|
||||
perror("bpf_obj_get_info_by_fd failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
xlated_prog_len = info.xlated_prog_len;
|
||||
if (xlated_prog_len % buf_element_size) {
|
||||
printf("Program length %d is not multiple of %d\n",
|
||||
xlated_prog_len, buf_element_size);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*cnt = xlated_prog_len / buf_element_size;
|
||||
*buf = calloc(*cnt, buf_element_size);
|
||||
if (!buf) {
|
||||
perror("can't allocate xlated program buffer");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
bzero(&info, sizeof(info));
|
||||
info.xlated_prog_len = xlated_prog_len;
|
||||
info.xlated_prog_insns = (__u64)*buf;
|
||||
if (bpf_obj_get_info_by_fd(fd_prog, &info, &info_len)) {
|
||||
perror("second bpf_obj_get_info_by_fd failed");
|
||||
goto out_free_buf;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_free_buf:
|
||||
free(*buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool is_null_insn(struct bpf_insn *insn)
|
||||
{
|
||||
struct bpf_insn null_insn = {};
|
||||
|
||||
return memcmp(insn, &null_insn, sizeof(null_insn)) == 0;
|
||||
}
|
||||
|
||||
static bool is_skip_insn(struct bpf_insn *insn)
|
||||
{
|
||||
struct bpf_insn skip_insn = SKIP_INSNS();
|
||||
|
||||
return memcmp(insn, &skip_insn, sizeof(skip_insn)) == 0;
|
||||
}
|
||||
|
||||
static int null_terminated_insn_len(struct bpf_insn *seq, int max_len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < max_len; ++i) {
|
||||
if (is_null_insn(&seq[i]))
|
||||
return i;
|
||||
}
|
||||
return max_len;
|
||||
}
|
||||
|
||||
static bool compare_masked_insn(struct bpf_insn *orig, struct bpf_insn *masked)
|
||||
{
|
||||
struct bpf_insn orig_masked;
|
||||
|
||||
memcpy(&orig_masked, orig, sizeof(orig_masked));
|
||||
if (masked->imm == INSN_IMM_MASK)
|
||||
orig_masked.imm = INSN_IMM_MASK;
|
||||
if (masked->off == INSN_OFF_MASK)
|
||||
orig_masked.off = INSN_OFF_MASK;
|
||||
|
||||
return memcmp(&orig_masked, masked, sizeof(orig_masked)) == 0;
|
||||
}
|
||||
|
||||
static int find_insn_subseq(struct bpf_insn *seq, struct bpf_insn *subseq,
|
||||
int seq_len, int subseq_len)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
if (subseq_len > seq_len)
|
||||
return -1;
|
||||
|
||||
for (i = 0; i < seq_len - subseq_len + 1; ++i) {
|
||||
bool found = true;
|
||||
|
||||
for (j = 0; j < subseq_len; ++j) {
|
||||
if (!compare_masked_insn(&seq[i + j], &subseq[j])) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int find_skip_insn_marker(struct bpf_insn *seq, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; ++i)
|
||||
if (is_skip_insn(&seq[i]))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Return true if all sub-sequences in `subseqs` could be found in
|
||||
* `seq` one after another. Sub-sequences are separated by a single
|
||||
* nil instruction.
|
||||
*/
|
||||
static bool find_all_insn_subseqs(struct bpf_insn *seq, struct bpf_insn *subseqs,
|
||||
int seq_len, int max_subseqs_len)
|
||||
{
|
||||
int subseqs_len = null_terminated_insn_len(subseqs, max_subseqs_len);
|
||||
|
||||
while (subseqs_len > 0) {
|
||||
int skip_idx = find_skip_insn_marker(subseqs, subseqs_len);
|
||||
int cur_subseq_len = skip_idx < 0 ? subseqs_len : skip_idx;
|
||||
int subseq_idx = find_insn_subseq(seq, subseqs,
|
||||
seq_len, cur_subseq_len);
|
||||
|
||||
if (subseq_idx < 0)
|
||||
return false;
|
||||
seq += subseq_idx + cur_subseq_len;
|
||||
seq_len -= subseq_idx + cur_subseq_len;
|
||||
subseqs += cur_subseq_len + 1;
|
||||
subseqs_len -= cur_subseq_len + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void print_insn(struct bpf_insn *buf, int cnt)
|
||||
{
|
||||
int i;
|
||||
|
||||
printf(" addr op d s off imm\n");
|
||||
for (i = 0; i < cnt; ++i) {
|
||||
struct bpf_insn *insn = &buf[i];
|
||||
|
||||
if (is_null_insn(insn))
|
||||
break;
|
||||
|
||||
if (is_skip_insn(insn))
|
||||
printf(" ...\n");
|
||||
else
|
||||
printf(" %04x: %02x %1x %x %04hx %08x\n",
|
||||
i, insn->code, insn->dst_reg,
|
||||
insn->src_reg, insn->off, insn->imm);
|
||||
}
|
||||
}
|
||||
|
||||
static bool check_xlated_program(struct bpf_test *test, int fd_prog)
|
||||
{
|
||||
struct bpf_insn *buf;
|
||||
int cnt;
|
||||
bool result = true;
|
||||
bool check_expected = !is_null_insn(test->expected_insns);
|
||||
bool check_unexpected = !is_null_insn(test->unexpected_insns);
|
||||
|
||||
if (!check_expected && !check_unexpected)
|
||||
goto out;
|
||||
|
||||
if (get_xlated_program(fd_prog, &buf, &cnt)) {
|
||||
printf("FAIL: can't get xlated program\n");
|
||||
result = false;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (check_expected &&
|
||||
!find_all_insn_subseqs(buf, test->expected_insns,
|
||||
cnt, MAX_EXPECTED_INSNS)) {
|
||||
printf("FAIL: can't find expected subsequence of instructions\n");
|
||||
result = false;
|
||||
if (verbose) {
|
||||
printf("Program:\n");
|
||||
print_insn(buf, cnt);
|
||||
printf("Expected subsequence:\n");
|
||||
print_insn(test->expected_insns, MAX_EXPECTED_INSNS);
|
||||
}
|
||||
}
|
||||
|
||||
if (check_unexpected &&
|
||||
find_all_insn_subseqs(buf, test->unexpected_insns,
|
||||
cnt, MAX_UNEXPECTED_INSNS)) {
|
||||
printf("FAIL: found unexpected subsequence of instructions\n");
|
||||
result = false;
|
||||
if (verbose) {
|
||||
printf("Program:\n");
|
||||
print_insn(buf, cnt);
|
||||
printf("Un-expected subsequence:\n");
|
||||
print_insn(test->unexpected_insns, MAX_UNEXPECTED_INSNS);
|
||||
}
|
||||
}
|
||||
|
||||
free(buf);
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
static void do_test_single(struct bpf_test *test, bool unpriv,
|
||||
int *passes, int *errors)
|
||||
{
|
||||
|
@ -1262,6 +1493,9 @@ static void do_test_single(struct bpf_test *test, bool unpriv,
|
|||
if (verbose)
|
||||
printf(", verifier log:\n%s", bpf_vlog);
|
||||
|
||||
if (!check_xlated_program(test, fd_prog))
|
||||
goto fail_log;
|
||||
|
||||
run_errs = 0;
|
||||
run_successes = 0;
|
||||
if (!alignment_prevented_execution && fd_prog >= 0 && test->runs >= 0) {
|
||||
|
|
Loading…
Add table
Reference in a new issue