linux/tools/testing/selftests/bpf/prog_tests/verifier_log.c
Alexei Starovoitov 957c30c067 selftests/bpf: adjust verifier_log buffers
The new liveness analysis in liveness.c adds verbose output at
BPF_LOG_LEVEL2, making the verifier log for good_prog exceed the 1024-byte
reference buffer. When the reference is truncated in fixed mode, the
rolling mode captures the actual tail of the full log, which doesn't match
the truncated reference.

The fix is to increase the buffer sizes in the test.

Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20260410-patch-set-v4-12-5d4eecb343db@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2026-04-10 15:13:38 -07:00

451 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */
#include <test_progs.h>
#include <bpf/btf.h>
#include "test_log_buf.skel.h"
static bool check_prog_load(int prog_fd, bool expect_err, const char *tag)
{
if (expect_err) {
if (!ASSERT_LT(prog_fd, 0, tag)) {
close(prog_fd);
return false;
}
} else /* !expect_err */ {
if (!ASSERT_GT(prog_fd, 0, tag))
return false;
}
if (prog_fd >= 0)
close(prog_fd);
return true;
}
static struct {
/* strategically placed before others to avoid accidental modification by kernel */
char filler[16384];
char buf[16384];
/* strategically placed after buf[] to catch more accidental corruptions */
char reference[16384];
} logs;
static const struct bpf_insn *insns;
static size_t insn_cnt;
static int load_prog(struct bpf_prog_load_opts *opts, bool expect_load_error)
{
int prog_fd;
prog_fd = bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT, "log_prog",
"GPL", insns, insn_cnt, opts);
check_prog_load(prog_fd, expect_load_error, "prog_load");
return prog_fd;
}
static void verif_log_subtest(const char *name, bool expect_load_error, int log_level)
{
LIBBPF_OPTS(bpf_prog_load_opts, opts);
char *exp_log, prog_name[24], op_name[32];
struct test_log_buf *skel;
struct bpf_program *prog;
size_t fixed_log_sz;
__u32 log_true_sz_fixed, log_true_sz_rolling;
int i, mode, err, prog_fd, res;
skel = test_log_buf__open();
if (!ASSERT_OK_PTR(skel, "skel_open"))
return;
bpf_object__for_each_program(prog, skel->obj) {
if (strcmp(bpf_program__name(prog), name) == 0)
bpf_program__set_autoload(prog, true);
else
bpf_program__set_autoload(prog, false);
}
err = test_log_buf__load(skel);
if (!expect_load_error && !ASSERT_OK(err, "unexpected_load_failure"))
goto cleanup;
if (expect_load_error && !ASSERT_ERR(err, "unexpected_load_success"))
goto cleanup;
insns = bpf_program__insns(skel->progs.good_prog);
insn_cnt = bpf_program__insn_cnt(skel->progs.good_prog);
opts.log_buf = logs.reference;
opts.log_size = sizeof(logs.reference);
opts.log_level = log_level | 8 /* BPF_LOG_FIXED */;
load_prog(&opts, expect_load_error);
fixed_log_sz = strlen(logs.reference) + 1;
if (!ASSERT_GT(fixed_log_sz, 50, "fixed_log_sz"))
goto cleanup;
memset(logs.reference + fixed_log_sz, 0, sizeof(logs.reference) - fixed_log_sz);
/* validate BPF_LOG_FIXED works as verifier log used to work, that is:
* we get -ENOSPC and beginning of the full verifier log. This only
* works for log_level 2 and log_level 1 + failed program. For log
* level 2 we don't reset log at all. For log_level 1 + failed program
* we don't get to verification stats output. With log level 1
* for successful program final result will be just verifier stats.
* But if provided too short log buf, kernel will NULL-out log->ubuf
* and will stop emitting further log. This means we'll never see
* predictable verifier stats.
* Long story short, we do the following -ENOSPC test only for
* predictable combinations.
*/
if (log_level >= 2 || expect_load_error) {
opts.log_buf = logs.buf;
opts.log_level = log_level | 8; /* fixed-length log */
opts.log_size = 25;
prog_fd = bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT, "log_fixed25",
"GPL", insns, insn_cnt, &opts);
if (!ASSERT_EQ(prog_fd, -ENOSPC, "unexpected_log_fixed_prog_load_result")) {
if (prog_fd >= 0)
close(prog_fd);
goto cleanup;
}
if (!ASSERT_EQ(strlen(logs.buf), 24, "log_fixed_25"))
goto cleanup;
if (!ASSERT_STRNEQ(logs.buf, logs.reference, 24, "log_fixed_contents_25"))
goto cleanup;
}
/* validate rolling verifier log logic: try all variations of log buf
* length to force various truncation scenarios
*/
opts.log_buf = logs.buf;
/* rotating mode, then fixed mode */
for (mode = 1; mode >= 0; mode--) {
/* prefill logs.buf with 'A's to detect any write beyond allowed length */
memset(logs.filler, 'A', sizeof(logs.filler));
logs.filler[sizeof(logs.filler) - 1] = '\0';
memset(logs.buf, 'A', sizeof(logs.buf));
logs.buf[sizeof(logs.buf) - 1] = '\0';
for (i = 1; i < fixed_log_sz; i++) {
opts.log_size = i;
opts.log_level = log_level | (mode ? 0 : 8 /* BPF_LOG_FIXED */);
snprintf(prog_name, sizeof(prog_name),
"log_%s_%d", mode ? "roll" : "fixed", i);
prog_fd = bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT, prog_name,
"GPL", insns, insn_cnt, &opts);
snprintf(op_name, sizeof(op_name),
"log_%s_prog_load_%d", mode ? "roll" : "fixed", i);
if (!ASSERT_EQ(prog_fd, -ENOSPC, op_name)) {
if (prog_fd >= 0)
close(prog_fd);
goto cleanup;
}
snprintf(op_name, sizeof(op_name),
"log_%s_strlen_%d", mode ? "roll" : "fixed", i);
ASSERT_EQ(strlen(logs.buf), i - 1, op_name);
if (mode)
exp_log = logs.reference + fixed_log_sz - i;
else
exp_log = logs.reference;
snprintf(op_name, sizeof(op_name),
"log_%s_contents_%d", mode ? "roll" : "fixed", i);
if (!ASSERT_STRNEQ(logs.buf, exp_log, i - 1, op_name)) {
printf("CMP:%d\nS1:'%s'\nS2:'%s'\n",
strncmp(logs.buf, exp_log, i - 1),
logs.buf, exp_log);
goto cleanup;
}
/* check that unused portions of logs.buf is not overwritten */
snprintf(op_name, sizeof(op_name),
"log_%s_unused_%d", mode ? "roll" : "fixed", i);
if (!ASSERT_STREQ(logs.buf + i, logs.filler + i, op_name)) {
printf("CMP:%d\nS1:'%s'\nS2:'%s'\n",
strcmp(logs.buf + i, logs.filler + i),
logs.buf + i, logs.filler + i);
goto cleanup;
}
}
}
/* (FIXED) get actual log size */
opts.log_buf = logs.buf;
opts.log_level = log_level | 8; /* BPF_LOG_FIXED */
opts.log_size = sizeof(logs.buf);
opts.log_true_size = 0;
res = load_prog(&opts, expect_load_error);
ASSERT_NEQ(res, -ENOSPC, "prog_load_res_fixed");
log_true_sz_fixed = opts.log_true_size;
ASSERT_GT(log_true_sz_fixed, 0, "log_true_sz_fixed");
/* (FIXED, NULL) get actual log size */
opts.log_buf = NULL;
opts.log_level = log_level | 8; /* BPF_LOG_FIXED */
opts.log_size = 0;
opts.log_true_size = 0;
res = load_prog(&opts, expect_load_error);
ASSERT_NEQ(res, -ENOSPC, "prog_load_res_fixed_null");
ASSERT_EQ(opts.log_true_size, log_true_sz_fixed, "log_sz_fixed_null_eq");
/* (ROLLING) get actual log size */
opts.log_buf = logs.buf;
opts.log_level = log_level;
opts.log_size = sizeof(logs.buf);
opts.log_true_size = 0;
res = load_prog(&opts, expect_load_error);
ASSERT_NEQ(res, -ENOSPC, "prog_load_res_rolling");
log_true_sz_rolling = opts.log_true_size;
ASSERT_EQ(log_true_sz_rolling, log_true_sz_fixed, "log_true_sz_eq");
/* (ROLLING, NULL) get actual log size */
opts.log_buf = NULL;
opts.log_level = log_level;
opts.log_size = 0;
opts.log_true_size = 0;
res = load_prog(&opts, expect_load_error);
ASSERT_NEQ(res, -ENOSPC, "prog_load_res_rolling_null");
ASSERT_EQ(opts.log_true_size, log_true_sz_rolling, "log_true_sz_null_eq");
/* (FIXED) expect -ENOSPC for one byte short log */
opts.log_buf = logs.buf;
opts.log_level = log_level | 8; /* BPF_LOG_FIXED */
opts.log_size = log_true_sz_fixed - 1;
opts.log_true_size = 0;
res = load_prog(&opts, true /* should fail */);
ASSERT_EQ(res, -ENOSPC, "prog_load_res_too_short_fixed");
/* (FIXED) expect *not* -ENOSPC with exact log_true_size buffer */
opts.log_buf = logs.buf;
opts.log_level = log_level | 8; /* BPF_LOG_FIXED */
opts.log_size = log_true_sz_fixed;
opts.log_true_size = 0;
res = load_prog(&opts, expect_load_error);
ASSERT_NEQ(res, -ENOSPC, "prog_load_res_just_right_fixed");
/* (ROLLING) expect -ENOSPC for one byte short log */
opts.log_buf = logs.buf;
opts.log_level = log_level;
opts.log_size = log_true_sz_rolling - 1;
res = load_prog(&opts, true /* should fail */);
ASSERT_EQ(res, -ENOSPC, "prog_load_res_too_short_rolling");
/* (ROLLING) expect *not* -ENOSPC with exact log_true_size buffer */
opts.log_buf = logs.buf;
opts.log_level = log_level;
opts.log_size = log_true_sz_rolling;
opts.log_true_size = 0;
res = load_prog(&opts, expect_load_error);
ASSERT_NEQ(res, -ENOSPC, "prog_load_res_just_right_rolling");
cleanup:
test_log_buf__destroy(skel);
}
static const void *btf_data;
static u32 btf_data_sz;
static int load_btf(struct bpf_btf_load_opts *opts, bool expect_err)
{
int fd;
fd = bpf_btf_load(btf_data, btf_data_sz, opts);
if (fd >= 0)
close(fd);
if (expect_err)
ASSERT_LT(fd, 0, "btf_load_failure");
else /* !expect_err */
ASSERT_GT(fd, 0, "btf_load_success");
return fd;
}
static void verif_btf_log_subtest(bool bad_btf)
{
LIBBPF_OPTS(bpf_btf_load_opts, opts);
struct btf *btf;
struct btf_type *t;
char *exp_log, op_name[32];
size_t fixed_log_sz;
__u32 log_true_sz_fixed, log_true_sz_rolling;
int i, res;
/* prepare simple BTF contents */
btf = btf__new_empty();
if (!ASSERT_OK_PTR(btf, "btf_new_empty"))
return;
res = btf__add_int(btf, "whatever", 4, 0);
if (!ASSERT_GT(res, 0, "btf_add_int_id"))
goto cleanup;
if (bad_btf) {
/* btf__add_int() doesn't allow bad value of size, so we'll just
* force-cast btf_type pointer and manually override size to invalid
* 3 if we need to simulate failure
*/
t = (void *)btf__type_by_id(btf, res);
if (!ASSERT_OK_PTR(t, "int_btf_type"))
goto cleanup;
t->size = 3;
}
btf_data = btf__raw_data(btf, &btf_data_sz);
if (!ASSERT_OK_PTR(btf_data, "btf_data"))
goto cleanup;
load_btf(&opts, bad_btf);
opts.log_buf = logs.reference;
opts.log_size = sizeof(logs.reference);
opts.log_level = 1 | 8 /* BPF_LOG_FIXED */;
load_btf(&opts, bad_btf);
fixed_log_sz = strlen(logs.reference) + 1;
if (!ASSERT_GT(fixed_log_sz, 50, "fixed_log_sz"))
goto cleanup;
memset(logs.reference + fixed_log_sz, 0, sizeof(logs.reference) - fixed_log_sz);
/* validate BPF_LOG_FIXED truncation works as verifier log used to work */
opts.log_buf = logs.buf;
opts.log_level = 1 | 8; /* fixed-length log */
opts.log_size = 25;
res = load_btf(&opts, true);
ASSERT_EQ(res, -ENOSPC, "half_log_fd");
ASSERT_EQ(strlen(logs.buf), 24, "log_fixed_25");
ASSERT_STRNEQ(logs.buf, logs.reference, 24, op_name);
/* validate rolling verifier log logic: try all variations of log buf
* length to force various truncation scenarios
*/
opts.log_buf = logs.buf;
opts.log_level = 1; /* rolling log */
/* prefill logs.buf with 'A's to detect any write beyond allowed length */
memset(logs.filler, 'A', sizeof(logs.filler));
logs.filler[sizeof(logs.filler) - 1] = '\0';
memset(logs.buf, 'A', sizeof(logs.buf));
logs.buf[sizeof(logs.buf) - 1] = '\0';
for (i = 1; i < fixed_log_sz; i++) {
opts.log_size = i;
snprintf(op_name, sizeof(op_name), "log_roll_btf_load_%d", i);
res = load_btf(&opts, true);
if (!ASSERT_EQ(res, -ENOSPC, op_name))
goto cleanup;
exp_log = logs.reference + fixed_log_sz - i;
snprintf(op_name, sizeof(op_name), "log_roll_contents_%d", i);
if (!ASSERT_STREQ(logs.buf, exp_log, op_name)) {
printf("CMP:%d\nS1:'%s'\nS2:'%s'\n",
strcmp(logs.buf, exp_log),
logs.buf, exp_log);
goto cleanup;
}
/* check that unused portions of logs.buf are not overwritten */
snprintf(op_name, sizeof(op_name), "log_roll_unused_tail_%d", i);
if (!ASSERT_STREQ(logs.buf + i, logs.filler + i, op_name)) {
printf("CMP:%d\nS1:'%s'\nS2:'%s'\n",
strcmp(logs.buf + i, logs.filler + i),
logs.buf + i, logs.filler + i);
goto cleanup;
}
}
/* (FIXED) get actual log size */
opts.log_buf = logs.buf;
opts.log_level = 1 | 8; /* BPF_LOG_FIXED */
opts.log_size = sizeof(logs.buf);
opts.log_true_size = 0;
res = load_btf(&opts, bad_btf);
ASSERT_NEQ(res, -ENOSPC, "btf_load_res_fixed");
log_true_sz_fixed = opts.log_true_size;
ASSERT_GT(log_true_sz_fixed, 0, "log_true_sz_fixed");
/* (FIXED, NULL) get actual log size */
opts.log_buf = NULL;
opts.log_level = 1 | 8; /* BPF_LOG_FIXED */
opts.log_size = 0;
opts.log_true_size = 0;
res = load_btf(&opts, bad_btf);
ASSERT_NEQ(res, -ENOSPC, "btf_load_res_fixed_null");
ASSERT_EQ(opts.log_true_size, log_true_sz_fixed, "log_sz_fixed_null_eq");
/* (ROLLING) get actual log size */
opts.log_buf = logs.buf;
opts.log_level = 1;
opts.log_size = sizeof(logs.buf);
opts.log_true_size = 0;
res = load_btf(&opts, bad_btf);
ASSERT_NEQ(res, -ENOSPC, "btf_load_res_rolling");
log_true_sz_rolling = opts.log_true_size;
ASSERT_EQ(log_true_sz_rolling, log_true_sz_fixed, "log_true_sz_eq");
/* (ROLLING, NULL) get actual log size */
opts.log_buf = NULL;
opts.log_level = 1;
opts.log_size = 0;
opts.log_true_size = 0;
res = load_btf(&opts, bad_btf);
ASSERT_NEQ(res, -ENOSPC, "btf_load_res_rolling_null");
ASSERT_EQ(opts.log_true_size, log_true_sz_rolling, "log_true_sz_null_eq");
/* (FIXED) expect -ENOSPC for one byte short log */
opts.log_buf = logs.buf;
opts.log_level = 1 | 8; /* BPF_LOG_FIXED */
opts.log_size = log_true_sz_fixed - 1;
opts.log_true_size = 0;
res = load_btf(&opts, true);
ASSERT_EQ(res, -ENOSPC, "btf_load_res_too_short_fixed");
/* (FIXED) expect *not* -ENOSPC with exact log_true_size buffer */
opts.log_buf = logs.buf;
opts.log_level = 1 | 8; /* BPF_LOG_FIXED */
opts.log_size = log_true_sz_fixed;
opts.log_true_size = 0;
res = load_btf(&opts, bad_btf);
ASSERT_NEQ(res, -ENOSPC, "btf_load_res_just_right_fixed");
/* (ROLLING) expect -ENOSPC for one byte short log */
opts.log_buf = logs.buf;
opts.log_level = 1;
opts.log_size = log_true_sz_rolling - 1;
res = load_btf(&opts, true);
ASSERT_EQ(res, -ENOSPC, "btf_load_res_too_short_rolling");
/* (ROLLING) expect *not* -ENOSPC with exact log_true_size buffer */
opts.log_buf = logs.buf;
opts.log_level = 1;
opts.log_size = log_true_sz_rolling;
opts.log_true_size = 0;
res = load_btf(&opts, bad_btf);
ASSERT_NEQ(res, -ENOSPC, "btf_load_res_just_right_rolling");
cleanup:
btf__free(btf);
}
void test_verifier_log(void)
{
if (test__start_subtest("good_prog-level1"))
verif_log_subtest("good_prog", false, 1);
if (test__start_subtest("good_prog-level2"))
verif_log_subtest("good_prog", false, 2);
if (test__start_subtest("bad_prog-level1"))
verif_log_subtest("bad_prog", true, 1);
if (test__start_subtest("bad_prog-level2"))
verif_log_subtest("bad_prog", true, 2);
if (test__start_subtest("bad_btf"))
verif_btf_log_subtest(true /* bad btf */);
if (test__start_subtest("good_btf"))
verif_btf_log_subtest(false /* !bad btf */);
}