bpf: move verifier state printing code to kernel/bpf/log.c
Move a good chunk of code from verifier.c to log.c: verifier state verbose printing logic. This is an important and very much logging/debugging oriented code. It fits the overlall log.c's focus on verifier logging, and moving it allows to keep growing it without unnecessarily adding to verifier.c code that otherwise contains a core verification logic. There are not many shared dependencies between this code and the rest of verifier.c code, except a few single-line helpers for various register type checks and a bit of state "scratching" helpers. We move all such trivial helpers into include/bpf/bpf_verifier.h as static inlines. No functional changes in this patch. Acked-by: Eduard Zingerman <eddyz87@gmail.com> Acked-by: Stanislav Fomichev <sdf@google.com> Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Link: https://lore.kernel.org/r/20231118034623.3320920-3-andrii@kernel.org Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
committed by
Alexei Starovoitov
parent
db840d389b
commit
42feb6620a
@@ -783,4 +783,76 @@ static inline bool bpf_type_has_unsafe_modifiers(u32 type)
|
||||
return type_flag(type) & ~BPF_REG_TRUSTED_MODIFIERS;
|
||||
}
|
||||
|
||||
static inline bool type_is_ptr_alloc_obj(u32 type)
|
||||
{
|
||||
return base_type(type) == PTR_TO_BTF_ID && type_flag(type) & MEM_ALLOC;
|
||||
}
|
||||
|
||||
static inline bool type_is_non_owning_ref(u32 type)
|
||||
{
|
||||
return type_is_ptr_alloc_obj(type) && type_flag(type) & NON_OWN_REF;
|
||||
}
|
||||
|
||||
static inline bool type_is_pkt_pointer(enum bpf_reg_type type)
|
||||
{
|
||||
type = base_type(type);
|
||||
return type == PTR_TO_PACKET ||
|
||||
type == PTR_TO_PACKET_META;
|
||||
}
|
||||
|
||||
static inline bool type_is_sk_pointer(enum bpf_reg_type type)
|
||||
{
|
||||
return type == PTR_TO_SOCKET ||
|
||||
type == PTR_TO_SOCK_COMMON ||
|
||||
type == PTR_TO_TCP_SOCK ||
|
||||
type == PTR_TO_XDP_SOCK;
|
||||
}
|
||||
|
||||
static inline void mark_reg_scratched(struct bpf_verifier_env *env, u32 regno)
|
||||
{
|
||||
env->scratched_regs |= 1U << regno;
|
||||
}
|
||||
|
||||
static inline void mark_stack_slot_scratched(struct bpf_verifier_env *env, u32 spi)
|
||||
{
|
||||
env->scratched_stack_slots |= 1ULL << spi;
|
||||
}
|
||||
|
||||
static inline bool reg_scratched(const struct bpf_verifier_env *env, u32 regno)
|
||||
{
|
||||
return (env->scratched_regs >> regno) & 1;
|
||||
}
|
||||
|
||||
static inline bool stack_slot_scratched(const struct bpf_verifier_env *env, u64 regno)
|
||||
{
|
||||
return (env->scratched_stack_slots >> regno) & 1;
|
||||
}
|
||||
|
||||
static inline bool verifier_state_scratched(const struct bpf_verifier_env *env)
|
||||
{
|
||||
return env->scratched_regs || env->scratched_stack_slots;
|
||||
}
|
||||
|
||||
static inline void mark_verifier_state_clean(struct bpf_verifier_env *env)
|
||||
{
|
||||
env->scratched_regs = 0U;
|
||||
env->scratched_stack_slots = 0ULL;
|
||||
}
|
||||
|
||||
/* Used for printing the entire verifier state. */
|
||||
static inline void mark_verifier_state_scratched(struct bpf_verifier_env *env)
|
||||
{
|
||||
env->scratched_regs = ~0U;
|
||||
env->scratched_stack_slots = ~0ULL;
|
||||
}
|
||||
|
||||
const char *reg_type_str(struct bpf_verifier_env *env, enum bpf_reg_type type);
|
||||
const char *dynptr_type_str(enum bpf_dynptr_type type);
|
||||
const char *iter_type_str(const struct btf *btf, u32 btf_id);
|
||||
const char *iter_state_str(enum bpf_iter_state state);
|
||||
|
||||
void print_verifier_state(struct bpf_verifier_env *env,
|
||||
const struct bpf_func_state *state, bool print_all);
|
||||
void print_insn_state(struct bpf_verifier_env *env, const struct bpf_func_state *state);
|
||||
|
||||
#endif /* _LINUX_BPF_VERIFIER_H */
|
||||
|
||||
@@ -384,3 +384,345 @@ __printf(3, 4) void verbose_linfo(struct bpf_verifier_env *env,
|
||||
|
||||
env->prev_linfo = linfo;
|
||||
}
|
||||
|
||||
static const char *btf_type_name(const struct btf *btf, u32 id)
|
||||
{
|
||||
return btf_name_by_offset(btf, btf_type_by_id(btf, id)->name_off);
|
||||
}
|
||||
|
||||
/* string representation of 'enum bpf_reg_type'
|
||||
*
|
||||
* Note that reg_type_str() can not appear more than once in a single verbose()
|
||||
* statement.
|
||||
*/
|
||||
const char *reg_type_str(struct bpf_verifier_env *env, enum bpf_reg_type type)
|
||||
{
|
||||
char postfix[16] = {0}, prefix[64] = {0};
|
||||
static const char * const str[] = {
|
||||
[NOT_INIT] = "?",
|
||||
[SCALAR_VALUE] = "scalar",
|
||||
[PTR_TO_CTX] = "ctx",
|
||||
[CONST_PTR_TO_MAP] = "map_ptr",
|
||||
[PTR_TO_MAP_VALUE] = "map_value",
|
||||
[PTR_TO_STACK] = "fp",
|
||||
[PTR_TO_PACKET] = "pkt",
|
||||
[PTR_TO_PACKET_META] = "pkt_meta",
|
||||
[PTR_TO_PACKET_END] = "pkt_end",
|
||||
[PTR_TO_FLOW_KEYS] = "flow_keys",
|
||||
[PTR_TO_SOCKET] = "sock",
|
||||
[PTR_TO_SOCK_COMMON] = "sock_common",
|
||||
[PTR_TO_TCP_SOCK] = "tcp_sock",
|
||||
[PTR_TO_TP_BUFFER] = "tp_buffer",
|
||||
[PTR_TO_XDP_SOCK] = "xdp_sock",
|
||||
[PTR_TO_BTF_ID] = "ptr_",
|
||||
[PTR_TO_MEM] = "mem",
|
||||
[PTR_TO_BUF] = "buf",
|
||||
[PTR_TO_FUNC] = "func",
|
||||
[PTR_TO_MAP_KEY] = "map_key",
|
||||
[CONST_PTR_TO_DYNPTR] = "dynptr_ptr",
|
||||
};
|
||||
|
||||
if (type & PTR_MAYBE_NULL) {
|
||||
if (base_type(type) == PTR_TO_BTF_ID)
|
||||
strncpy(postfix, "or_null_", 16);
|
||||
else
|
||||
strncpy(postfix, "_or_null", 16);
|
||||
}
|
||||
|
||||
snprintf(prefix, sizeof(prefix), "%s%s%s%s%s%s%s",
|
||||
type & MEM_RDONLY ? "rdonly_" : "",
|
||||
type & MEM_RINGBUF ? "ringbuf_" : "",
|
||||
type & MEM_USER ? "user_" : "",
|
||||
type & MEM_PERCPU ? "percpu_" : "",
|
||||
type & MEM_RCU ? "rcu_" : "",
|
||||
type & PTR_UNTRUSTED ? "untrusted_" : "",
|
||||
type & PTR_TRUSTED ? "trusted_" : ""
|
||||
);
|
||||
|
||||
snprintf(env->tmp_str_buf, TMP_STR_BUF_LEN, "%s%s%s",
|
||||
prefix, str[base_type(type)], postfix);
|
||||
return env->tmp_str_buf;
|
||||
}
|
||||
|
||||
const char *dynptr_type_str(enum bpf_dynptr_type type)
|
||||
{
|
||||
switch (type) {
|
||||
case BPF_DYNPTR_TYPE_LOCAL:
|
||||
return "local";
|
||||
case BPF_DYNPTR_TYPE_RINGBUF:
|
||||
return "ringbuf";
|
||||
case BPF_DYNPTR_TYPE_SKB:
|
||||
return "skb";
|
||||
case BPF_DYNPTR_TYPE_XDP:
|
||||
return "xdp";
|
||||
case BPF_DYNPTR_TYPE_INVALID:
|
||||
return "<invalid>";
|
||||
default:
|
||||
WARN_ONCE(1, "unknown dynptr type %d\n", type);
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
const char *iter_type_str(const struct btf *btf, u32 btf_id)
|
||||
{
|
||||
if (!btf || btf_id == 0)
|
||||
return "<invalid>";
|
||||
|
||||
/* we already validated that type is valid and has conforming name */
|
||||
return btf_type_name(btf, btf_id) + sizeof(ITER_PREFIX) - 1;
|
||||
}
|
||||
|
||||
const char *iter_state_str(enum bpf_iter_state state)
|
||||
{
|
||||
switch (state) {
|
||||
case BPF_ITER_STATE_ACTIVE:
|
||||
return "active";
|
||||
case BPF_ITER_STATE_DRAINED:
|
||||
return "drained";
|
||||
case BPF_ITER_STATE_INVALID:
|
||||
return "<invalid>";
|
||||
default:
|
||||
WARN_ONCE(1, "unknown iter state %d\n", state);
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
static char slot_type_char[] = {
|
||||
[STACK_INVALID] = '?',
|
||||
[STACK_SPILL] = 'r',
|
||||
[STACK_MISC] = 'm',
|
||||
[STACK_ZERO] = '0',
|
||||
[STACK_DYNPTR] = 'd',
|
||||
[STACK_ITER] = 'i',
|
||||
};
|
||||
|
||||
static void print_liveness(struct bpf_verifier_env *env,
|
||||
enum bpf_reg_liveness live)
|
||||
{
|
||||
if (live & (REG_LIVE_READ | REG_LIVE_WRITTEN | REG_LIVE_DONE))
|
||||
verbose(env, "_");
|
||||
if (live & REG_LIVE_READ)
|
||||
verbose(env, "r");
|
||||
if (live & REG_LIVE_WRITTEN)
|
||||
verbose(env, "w");
|
||||
if (live & REG_LIVE_DONE)
|
||||
verbose(env, "D");
|
||||
}
|
||||
|
||||
static void print_scalar_ranges(struct bpf_verifier_env *env,
|
||||
const struct bpf_reg_state *reg,
|
||||
const char **sep)
|
||||
{
|
||||
struct {
|
||||
const char *name;
|
||||
u64 val;
|
||||
bool omit;
|
||||
} minmaxs[] = {
|
||||
{"smin", reg->smin_value, reg->smin_value == S64_MIN},
|
||||
{"smax", reg->smax_value, reg->smax_value == S64_MAX},
|
||||
{"umin", reg->umin_value, reg->umin_value == 0},
|
||||
{"umax", reg->umax_value, reg->umax_value == U64_MAX},
|
||||
{"smin32", (s64)reg->s32_min_value, reg->s32_min_value == S32_MIN},
|
||||
{"smax32", (s64)reg->s32_max_value, reg->s32_max_value == S32_MAX},
|
||||
{"umin32", reg->u32_min_value, reg->u32_min_value == 0},
|
||||
{"umax32", reg->u32_max_value, reg->u32_max_value == U32_MAX},
|
||||
}, *m1, *m2, *mend = &minmaxs[ARRAY_SIZE(minmaxs)];
|
||||
bool neg1, neg2;
|
||||
|
||||
for (m1 = &minmaxs[0]; m1 < mend; m1++) {
|
||||
if (m1->omit)
|
||||
continue;
|
||||
|
||||
neg1 = m1->name[0] == 's' && (s64)m1->val < 0;
|
||||
|
||||
verbose(env, "%s%s=", *sep, m1->name);
|
||||
*sep = ",";
|
||||
|
||||
for (m2 = m1 + 2; m2 < mend; m2 += 2) {
|
||||
if (m2->omit || m2->val != m1->val)
|
||||
continue;
|
||||
/* don't mix negatives with positives */
|
||||
neg2 = m2->name[0] == 's' && (s64)m2->val < 0;
|
||||
if (neg2 != neg1)
|
||||
continue;
|
||||
m2->omit = true;
|
||||
verbose(env, "%s=", m2->name);
|
||||
}
|
||||
|
||||
verbose(env, m1->name[0] == 's' ? "%lld" : "%llu", m1->val);
|
||||
}
|
||||
}
|
||||
|
||||
void print_verifier_state(struct bpf_verifier_env *env, const struct bpf_func_state *state,
|
||||
bool print_all)
|
||||
{
|
||||
const struct bpf_reg_state *reg;
|
||||
enum bpf_reg_type t;
|
||||
int i;
|
||||
|
||||
if (state->frameno)
|
||||
verbose(env, " frame%d:", state->frameno);
|
||||
for (i = 0; i < MAX_BPF_REG; i++) {
|
||||
reg = &state->regs[i];
|
||||
t = reg->type;
|
||||
if (t == NOT_INIT)
|
||||
continue;
|
||||
if (!print_all && !reg_scratched(env, i))
|
||||
continue;
|
||||
verbose(env, " R%d", i);
|
||||
print_liveness(env, reg->live);
|
||||
verbose(env, "=");
|
||||
if (t == SCALAR_VALUE && reg->precise)
|
||||
verbose(env, "P");
|
||||
if ((t == SCALAR_VALUE || t == PTR_TO_STACK) &&
|
||||
tnum_is_const(reg->var_off)) {
|
||||
/* reg->off should be 0 for SCALAR_VALUE */
|
||||
verbose(env, "%s", t == SCALAR_VALUE ? "" : reg_type_str(env, t));
|
||||
verbose(env, "%lld", reg->var_off.value + reg->off);
|
||||
} else {
|
||||
const char *sep = "";
|
||||
|
||||
verbose(env, "%s", reg_type_str(env, t));
|
||||
if (base_type(t) == PTR_TO_BTF_ID)
|
||||
verbose(env, "%s", btf_type_name(reg->btf, reg->btf_id));
|
||||
verbose(env, "(");
|
||||
/*
|
||||
* _a stands for append, was shortened to avoid multiline statements below.
|
||||
* This macro is used to output a comma separated list of attributes.
|
||||
*/
|
||||
#define verbose_a(fmt, ...) ({ verbose(env, "%s" fmt, sep, __VA_ARGS__); sep = ","; })
|
||||
|
||||
if (reg->id)
|
||||
verbose_a("id=%d", reg->id);
|
||||
if (reg->ref_obj_id)
|
||||
verbose_a("ref_obj_id=%d", reg->ref_obj_id);
|
||||
if (type_is_non_owning_ref(reg->type))
|
||||
verbose_a("%s", "non_own_ref");
|
||||
if (t != SCALAR_VALUE)
|
||||
verbose_a("off=%d", reg->off);
|
||||
if (type_is_pkt_pointer(t))
|
||||
verbose_a("r=%d", reg->range);
|
||||
else if (base_type(t) == CONST_PTR_TO_MAP ||
|
||||
base_type(t) == PTR_TO_MAP_KEY ||
|
||||
base_type(t) == PTR_TO_MAP_VALUE)
|
||||
verbose_a("ks=%d,vs=%d",
|
||||
reg->map_ptr->key_size,
|
||||
reg->map_ptr->value_size);
|
||||
if (tnum_is_const(reg->var_off)) {
|
||||
/* Typically an immediate SCALAR_VALUE, but
|
||||
* could be a pointer whose offset is too big
|
||||
* for reg->off
|
||||
*/
|
||||
verbose_a("imm=%llx", reg->var_off.value);
|
||||
} else {
|
||||
print_scalar_ranges(env, reg, &sep);
|
||||
if (!tnum_is_unknown(reg->var_off)) {
|
||||
char tn_buf[48];
|
||||
|
||||
tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
|
||||
verbose_a("var_off=%s", tn_buf);
|
||||
}
|
||||
}
|
||||
#undef verbose_a
|
||||
|
||||
verbose(env, ")");
|
||||
}
|
||||
}
|
||||
for (i = 0; i < state->allocated_stack / BPF_REG_SIZE; i++) {
|
||||
char types_buf[BPF_REG_SIZE + 1];
|
||||
bool valid = false;
|
||||
int j;
|
||||
|
||||
for (j = 0; j < BPF_REG_SIZE; j++) {
|
||||
if (state->stack[i].slot_type[j] != STACK_INVALID)
|
||||
valid = true;
|
||||
types_buf[j] = slot_type_char[state->stack[i].slot_type[j]];
|
||||
}
|
||||
types_buf[BPF_REG_SIZE] = 0;
|
||||
if (!valid)
|
||||
continue;
|
||||
if (!print_all && !stack_slot_scratched(env, i))
|
||||
continue;
|
||||
switch (state->stack[i].slot_type[BPF_REG_SIZE - 1]) {
|
||||
case STACK_SPILL:
|
||||
reg = &state->stack[i].spilled_ptr;
|
||||
t = reg->type;
|
||||
|
||||
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
|
||||
print_liveness(env, reg->live);
|
||||
verbose(env, "=%s", t == SCALAR_VALUE ? "" : reg_type_str(env, t));
|
||||
if (t == SCALAR_VALUE && reg->precise)
|
||||
verbose(env, "P");
|
||||
if (t == SCALAR_VALUE && tnum_is_const(reg->var_off))
|
||||
verbose(env, "%lld", reg->var_off.value + reg->off);
|
||||
break;
|
||||
case STACK_DYNPTR:
|
||||
i += BPF_DYNPTR_NR_SLOTS - 1;
|
||||
reg = &state->stack[i].spilled_ptr;
|
||||
|
||||
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
|
||||
print_liveness(env, reg->live);
|
||||
verbose(env, "=dynptr_%s", dynptr_type_str(reg->dynptr.type));
|
||||
if (reg->ref_obj_id)
|
||||
verbose(env, "(ref_id=%d)", reg->ref_obj_id);
|
||||
break;
|
||||
case STACK_ITER:
|
||||
/* only main slot has ref_obj_id set; skip others */
|
||||
reg = &state->stack[i].spilled_ptr;
|
||||
if (!reg->ref_obj_id)
|
||||
continue;
|
||||
|
||||
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
|
||||
print_liveness(env, reg->live);
|
||||
verbose(env, "=iter_%s(ref_id=%d,state=%s,depth=%u)",
|
||||
iter_type_str(reg->iter.btf, reg->iter.btf_id),
|
||||
reg->ref_obj_id, iter_state_str(reg->iter.state),
|
||||
reg->iter.depth);
|
||||
break;
|
||||
case STACK_MISC:
|
||||
case STACK_ZERO:
|
||||
default:
|
||||
reg = &state->stack[i].spilled_ptr;
|
||||
|
||||
for (j = 0; j < BPF_REG_SIZE; j++)
|
||||
types_buf[j] = slot_type_char[state->stack[i].slot_type[j]];
|
||||
types_buf[BPF_REG_SIZE] = 0;
|
||||
|
||||
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
|
||||
print_liveness(env, reg->live);
|
||||
verbose(env, "=%s", types_buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (state->acquired_refs && state->refs[0].id) {
|
||||
verbose(env, " refs=%d", state->refs[0].id);
|
||||
for (i = 1; i < state->acquired_refs; i++)
|
||||
if (state->refs[i].id)
|
||||
verbose(env, ",%d", state->refs[i].id);
|
||||
}
|
||||
if (state->in_callback_fn)
|
||||
verbose(env, " cb");
|
||||
if (state->in_async_callback_fn)
|
||||
verbose(env, " async_cb");
|
||||
verbose(env, "\n");
|
||||
if (!print_all)
|
||||
mark_verifier_state_clean(env);
|
||||
}
|
||||
|
||||
static inline u32 vlog_alignment(u32 pos)
|
||||
{
|
||||
return round_up(max(pos + BPF_LOG_MIN_ALIGNMENT / 2, BPF_LOG_ALIGNMENT),
|
||||
BPF_LOG_MIN_ALIGNMENT) - pos - 1;
|
||||
}
|
||||
|
||||
void print_insn_state(struct bpf_verifier_env *env, const struct bpf_func_state *state)
|
||||
{
|
||||
if (env->prev_log_pos && env->prev_log_pos == env->log.end_pos) {
|
||||
/* remove new line character */
|
||||
bpf_vlog_reset(&env->log, env->prev_log_pos - 1);
|
||||
verbose(env, "%*c;", vlog_alignment(env->prev_insn_print_pos), ' ');
|
||||
} else {
|
||||
verbose(env, "%d:", env->insn_idx);
|
||||
}
|
||||
print_verifier_state(env, state, false);
|
||||
}
|
||||
|
||||
@@ -368,21 +368,6 @@ static void verbose_invalid_scalar(struct bpf_verifier_env *env,
|
||||
verbose(env, " should have been in %s\n", tn_buf);
|
||||
}
|
||||
|
||||
static bool type_is_pkt_pointer(enum bpf_reg_type type)
|
||||
{
|
||||
type = base_type(type);
|
||||
return type == PTR_TO_PACKET ||
|
||||
type == PTR_TO_PACKET_META;
|
||||
}
|
||||
|
||||
static bool type_is_sk_pointer(enum bpf_reg_type type)
|
||||
{
|
||||
return type == PTR_TO_SOCKET ||
|
||||
type == PTR_TO_SOCK_COMMON ||
|
||||
type == PTR_TO_TCP_SOCK ||
|
||||
type == PTR_TO_XDP_SOCK;
|
||||
}
|
||||
|
||||
static bool type_may_be_null(u32 type)
|
||||
{
|
||||
return type & PTR_MAYBE_NULL;
|
||||
@@ -406,16 +391,6 @@ static bool reg_not_null(const struct bpf_reg_state *reg)
|
||||
type == PTR_TO_MEM;
|
||||
}
|
||||
|
||||
static bool type_is_ptr_alloc_obj(u32 type)
|
||||
{
|
||||
return base_type(type) == PTR_TO_BTF_ID && type_flag(type) & MEM_ALLOC;
|
||||
}
|
||||
|
||||
static bool type_is_non_owning_ref(u32 type)
|
||||
{
|
||||
return type_is_ptr_alloc_obj(type) && type_flag(type) & NON_OWN_REF;
|
||||
}
|
||||
|
||||
static struct btf_record *reg_btf_record(const struct bpf_reg_state *reg)
|
||||
{
|
||||
struct btf_record *rec = NULL;
|
||||
@@ -532,83 +507,6 @@ static bool is_cmpxchg_insn(const struct bpf_insn *insn)
|
||||
insn->imm == BPF_CMPXCHG;
|
||||
}
|
||||
|
||||
/* string representation of 'enum bpf_reg_type'
|
||||
*
|
||||
* Note that reg_type_str() can not appear more than once in a single verbose()
|
||||
* statement.
|
||||
*/
|
||||
static const char *reg_type_str(struct bpf_verifier_env *env,
|
||||
enum bpf_reg_type type)
|
||||
{
|
||||
char postfix[16] = {0}, prefix[64] = {0};
|
||||
static const char * const str[] = {
|
||||
[NOT_INIT] = "?",
|
||||
[SCALAR_VALUE] = "scalar",
|
||||
[PTR_TO_CTX] = "ctx",
|
||||
[CONST_PTR_TO_MAP] = "map_ptr",
|
||||
[PTR_TO_MAP_VALUE] = "map_value",
|
||||
[PTR_TO_STACK] = "fp",
|
||||
[PTR_TO_PACKET] = "pkt",
|
||||
[PTR_TO_PACKET_META] = "pkt_meta",
|
||||
[PTR_TO_PACKET_END] = "pkt_end",
|
||||
[PTR_TO_FLOW_KEYS] = "flow_keys",
|
||||
[PTR_TO_SOCKET] = "sock",
|
||||
[PTR_TO_SOCK_COMMON] = "sock_common",
|
||||
[PTR_TO_TCP_SOCK] = "tcp_sock",
|
||||
[PTR_TO_TP_BUFFER] = "tp_buffer",
|
||||
[PTR_TO_XDP_SOCK] = "xdp_sock",
|
||||
[PTR_TO_BTF_ID] = "ptr_",
|
||||
[PTR_TO_MEM] = "mem",
|
||||
[PTR_TO_BUF] = "buf",
|
||||
[PTR_TO_FUNC] = "func",
|
||||
[PTR_TO_MAP_KEY] = "map_key",
|
||||
[CONST_PTR_TO_DYNPTR] = "dynptr_ptr",
|
||||
};
|
||||
|
||||
if (type & PTR_MAYBE_NULL) {
|
||||
if (base_type(type) == PTR_TO_BTF_ID)
|
||||
strncpy(postfix, "or_null_", 16);
|
||||
else
|
||||
strncpy(postfix, "_or_null", 16);
|
||||
}
|
||||
|
||||
snprintf(prefix, sizeof(prefix), "%s%s%s%s%s%s%s",
|
||||
type & MEM_RDONLY ? "rdonly_" : "",
|
||||
type & MEM_RINGBUF ? "ringbuf_" : "",
|
||||
type & MEM_USER ? "user_" : "",
|
||||
type & MEM_PERCPU ? "percpu_" : "",
|
||||
type & MEM_RCU ? "rcu_" : "",
|
||||
type & PTR_UNTRUSTED ? "untrusted_" : "",
|
||||
type & PTR_TRUSTED ? "trusted_" : ""
|
||||
);
|
||||
|
||||
snprintf(env->tmp_str_buf, TMP_STR_BUF_LEN, "%s%s%s",
|
||||
prefix, str[base_type(type)], postfix);
|
||||
return env->tmp_str_buf;
|
||||
}
|
||||
|
||||
static char slot_type_char[] = {
|
||||
[STACK_INVALID] = '?',
|
||||
[STACK_SPILL] = 'r',
|
||||
[STACK_MISC] = 'm',
|
||||
[STACK_ZERO] = '0',
|
||||
[STACK_DYNPTR] = 'd',
|
||||
[STACK_ITER] = 'i',
|
||||
};
|
||||
|
||||
static void print_liveness(struct bpf_verifier_env *env,
|
||||
enum bpf_reg_liveness live)
|
||||
{
|
||||
if (live & (REG_LIVE_READ | REG_LIVE_WRITTEN | REG_LIVE_DONE))
|
||||
verbose(env, "_");
|
||||
if (live & REG_LIVE_READ)
|
||||
verbose(env, "r");
|
||||
if (live & REG_LIVE_WRITTEN)
|
||||
verbose(env, "w");
|
||||
if (live & REG_LIVE_DONE)
|
||||
verbose(env, "D");
|
||||
}
|
||||
|
||||
static int __get_spi(s32 off)
|
||||
{
|
||||
return (-off - 1) / BPF_REG_SIZE;
|
||||
@@ -678,87 +576,6 @@ static const char *btf_type_name(const struct btf *btf, u32 id)
|
||||
return btf_name_by_offset(btf, btf_type_by_id(btf, id)->name_off);
|
||||
}
|
||||
|
||||
static const char *dynptr_type_str(enum bpf_dynptr_type type)
|
||||
{
|
||||
switch (type) {
|
||||
case BPF_DYNPTR_TYPE_LOCAL:
|
||||
return "local";
|
||||
case BPF_DYNPTR_TYPE_RINGBUF:
|
||||
return "ringbuf";
|
||||
case BPF_DYNPTR_TYPE_SKB:
|
||||
return "skb";
|
||||
case BPF_DYNPTR_TYPE_XDP:
|
||||
return "xdp";
|
||||
case BPF_DYNPTR_TYPE_INVALID:
|
||||
return "<invalid>";
|
||||
default:
|
||||
WARN_ONCE(1, "unknown dynptr type %d\n", type);
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *iter_type_str(const struct btf *btf, u32 btf_id)
|
||||
{
|
||||
if (!btf || btf_id == 0)
|
||||
return "<invalid>";
|
||||
|
||||
/* we already validated that type is valid and has conforming name */
|
||||
return btf_type_name(btf, btf_id) + sizeof(ITER_PREFIX) - 1;
|
||||
}
|
||||
|
||||
static const char *iter_state_str(enum bpf_iter_state state)
|
||||
{
|
||||
switch (state) {
|
||||
case BPF_ITER_STATE_ACTIVE:
|
||||
return "active";
|
||||
case BPF_ITER_STATE_DRAINED:
|
||||
return "drained";
|
||||
case BPF_ITER_STATE_INVALID:
|
||||
return "<invalid>";
|
||||
default:
|
||||
WARN_ONCE(1, "unknown iter state %d\n", state);
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_reg_scratched(struct bpf_verifier_env *env, u32 regno)
|
||||
{
|
||||
env->scratched_regs |= 1U << regno;
|
||||
}
|
||||
|
||||
static void mark_stack_slot_scratched(struct bpf_verifier_env *env, u32 spi)
|
||||
{
|
||||
env->scratched_stack_slots |= 1ULL << spi;
|
||||
}
|
||||
|
||||
static bool reg_scratched(const struct bpf_verifier_env *env, u32 regno)
|
||||
{
|
||||
return (env->scratched_regs >> regno) & 1;
|
||||
}
|
||||
|
||||
static bool stack_slot_scratched(const struct bpf_verifier_env *env, u64 regno)
|
||||
{
|
||||
return (env->scratched_stack_slots >> regno) & 1;
|
||||
}
|
||||
|
||||
static bool verifier_state_scratched(const struct bpf_verifier_env *env)
|
||||
{
|
||||
return env->scratched_regs || env->scratched_stack_slots;
|
||||
}
|
||||
|
||||
static void mark_verifier_state_clean(struct bpf_verifier_env *env)
|
||||
{
|
||||
env->scratched_regs = 0U;
|
||||
env->scratched_stack_slots = 0ULL;
|
||||
}
|
||||
|
||||
/* Used for printing the entire verifier state. */
|
||||
static void mark_verifier_state_scratched(struct bpf_verifier_env *env)
|
||||
{
|
||||
env->scratched_regs = ~0U;
|
||||
env->scratched_stack_slots = ~0ULL;
|
||||
}
|
||||
|
||||
static enum bpf_dynptr_type arg_to_dynptr_type(enum bpf_arg_type arg_type)
|
||||
{
|
||||
switch (arg_type & DYNPTR_TYPE_FLAG_MASK) {
|
||||
@@ -1298,226 +1115,6 @@ static void scrub_spilled_slot(u8 *stype)
|
||||
*stype = STACK_MISC;
|
||||
}
|
||||
|
||||
static void print_scalar_ranges(struct bpf_verifier_env *env,
|
||||
const struct bpf_reg_state *reg,
|
||||
const char **sep)
|
||||
{
|
||||
struct {
|
||||
const char *name;
|
||||
u64 val;
|
||||
bool omit;
|
||||
} minmaxs[] = {
|
||||
{"smin", reg->smin_value, reg->smin_value == S64_MIN},
|
||||
{"smax", reg->smax_value, reg->smax_value == S64_MAX},
|
||||
{"umin", reg->umin_value, reg->umin_value == 0},
|
||||
{"umax", reg->umax_value, reg->umax_value == U64_MAX},
|
||||
{"smin32", (s64)reg->s32_min_value, reg->s32_min_value == S32_MIN},
|
||||
{"smax32", (s64)reg->s32_max_value, reg->s32_max_value == S32_MAX},
|
||||
{"umin32", reg->u32_min_value, reg->u32_min_value == 0},
|
||||
{"umax32", reg->u32_max_value, reg->u32_max_value == U32_MAX},
|
||||
}, *m1, *m2, *mend = &minmaxs[ARRAY_SIZE(minmaxs)];
|
||||
bool neg1, neg2;
|
||||
|
||||
for (m1 = &minmaxs[0]; m1 < mend; m1++) {
|
||||
if (m1->omit)
|
||||
continue;
|
||||
|
||||
neg1 = m1->name[0] == 's' && (s64)m1->val < 0;
|
||||
|
||||
verbose(env, "%s%s=", *sep, m1->name);
|
||||
*sep = ",";
|
||||
|
||||
for (m2 = m1 + 2; m2 < mend; m2 += 2) {
|
||||
if (m2->omit || m2->val != m1->val)
|
||||
continue;
|
||||
/* don't mix negatives with positives */
|
||||
neg2 = m2->name[0] == 's' && (s64)m2->val < 0;
|
||||
if (neg2 != neg1)
|
||||
continue;
|
||||
m2->omit = true;
|
||||
verbose(env, "%s=", m2->name);
|
||||
}
|
||||
|
||||
verbose(env, m1->name[0] == 's' ? "%lld" : "%llu", m1->val);
|
||||
}
|
||||
}
|
||||
|
||||
static void print_verifier_state(struct bpf_verifier_env *env,
|
||||
const struct bpf_func_state *state,
|
||||
bool print_all)
|
||||
{
|
||||
const struct bpf_reg_state *reg;
|
||||
enum bpf_reg_type t;
|
||||
int i;
|
||||
|
||||
if (state->frameno)
|
||||
verbose(env, " frame%d:", state->frameno);
|
||||
for (i = 0; i < MAX_BPF_REG; i++) {
|
||||
reg = &state->regs[i];
|
||||
t = reg->type;
|
||||
if (t == NOT_INIT)
|
||||
continue;
|
||||
if (!print_all && !reg_scratched(env, i))
|
||||
continue;
|
||||
verbose(env, " R%d", i);
|
||||
print_liveness(env, reg->live);
|
||||
verbose(env, "=");
|
||||
if (t == SCALAR_VALUE && reg->precise)
|
||||
verbose(env, "P");
|
||||
if ((t == SCALAR_VALUE || t == PTR_TO_STACK) &&
|
||||
tnum_is_const(reg->var_off)) {
|
||||
/* reg->off should be 0 for SCALAR_VALUE */
|
||||
verbose(env, "%s", t == SCALAR_VALUE ? "" : reg_type_str(env, t));
|
||||
verbose(env, "%lld", reg->var_off.value + reg->off);
|
||||
} else {
|
||||
const char *sep = "";
|
||||
|
||||
verbose(env, "%s", reg_type_str(env, t));
|
||||
if (base_type(t) == PTR_TO_BTF_ID)
|
||||
verbose(env, "%s", btf_type_name(reg->btf, reg->btf_id));
|
||||
verbose(env, "(");
|
||||
/*
|
||||
* _a stands for append, was shortened to avoid multiline statements below.
|
||||
* This macro is used to output a comma separated list of attributes.
|
||||
*/
|
||||
#define verbose_a(fmt, ...) ({ verbose(env, "%s" fmt, sep, __VA_ARGS__); sep = ","; })
|
||||
|
||||
if (reg->id)
|
||||
verbose_a("id=%d", reg->id);
|
||||
if (reg->ref_obj_id)
|
||||
verbose_a("ref_obj_id=%d", reg->ref_obj_id);
|
||||
if (type_is_non_owning_ref(reg->type))
|
||||
verbose_a("%s", "non_own_ref");
|
||||
if (t != SCALAR_VALUE)
|
||||
verbose_a("off=%d", reg->off);
|
||||
if (type_is_pkt_pointer(t))
|
||||
verbose_a("r=%d", reg->range);
|
||||
else if (base_type(t) == CONST_PTR_TO_MAP ||
|
||||
base_type(t) == PTR_TO_MAP_KEY ||
|
||||
base_type(t) == PTR_TO_MAP_VALUE)
|
||||
verbose_a("ks=%d,vs=%d",
|
||||
reg->map_ptr->key_size,
|
||||
reg->map_ptr->value_size);
|
||||
if (tnum_is_const(reg->var_off)) {
|
||||
/* Typically an immediate SCALAR_VALUE, but
|
||||
* could be a pointer whose offset is too big
|
||||
* for reg->off
|
||||
*/
|
||||
verbose_a("imm=%llx", reg->var_off.value);
|
||||
} else {
|
||||
print_scalar_ranges(env, reg, &sep);
|
||||
if (!tnum_is_unknown(reg->var_off)) {
|
||||
char tn_buf[48];
|
||||
|
||||
tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
|
||||
verbose_a("var_off=%s", tn_buf);
|
||||
}
|
||||
}
|
||||
#undef verbose_a
|
||||
|
||||
verbose(env, ")");
|
||||
}
|
||||
}
|
||||
for (i = 0; i < state->allocated_stack / BPF_REG_SIZE; i++) {
|
||||
char types_buf[BPF_REG_SIZE + 1];
|
||||
bool valid = false;
|
||||
int j;
|
||||
|
||||
for (j = 0; j < BPF_REG_SIZE; j++) {
|
||||
if (state->stack[i].slot_type[j] != STACK_INVALID)
|
||||
valid = true;
|
||||
types_buf[j] = slot_type_char[state->stack[i].slot_type[j]];
|
||||
}
|
||||
types_buf[BPF_REG_SIZE] = 0;
|
||||
if (!valid)
|
||||
continue;
|
||||
if (!print_all && !stack_slot_scratched(env, i))
|
||||
continue;
|
||||
switch (state->stack[i].slot_type[BPF_REG_SIZE - 1]) {
|
||||
case STACK_SPILL:
|
||||
reg = &state->stack[i].spilled_ptr;
|
||||
t = reg->type;
|
||||
|
||||
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
|
||||
print_liveness(env, reg->live);
|
||||
verbose(env, "=%s", t == SCALAR_VALUE ? "" : reg_type_str(env, t));
|
||||
if (t == SCALAR_VALUE && reg->precise)
|
||||
verbose(env, "P");
|
||||
if (t == SCALAR_VALUE && tnum_is_const(reg->var_off))
|
||||
verbose(env, "%lld", reg->var_off.value + reg->off);
|
||||
break;
|
||||
case STACK_DYNPTR:
|
||||
i += BPF_DYNPTR_NR_SLOTS - 1;
|
||||
reg = &state->stack[i].spilled_ptr;
|
||||
|
||||
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
|
||||
print_liveness(env, reg->live);
|
||||
verbose(env, "=dynptr_%s", dynptr_type_str(reg->dynptr.type));
|
||||
if (reg->ref_obj_id)
|
||||
verbose(env, "(ref_id=%d)", reg->ref_obj_id);
|
||||
break;
|
||||
case STACK_ITER:
|
||||
/* only main slot has ref_obj_id set; skip others */
|
||||
reg = &state->stack[i].spilled_ptr;
|
||||
if (!reg->ref_obj_id)
|
||||
continue;
|
||||
|
||||
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
|
||||
print_liveness(env, reg->live);
|
||||
verbose(env, "=iter_%s(ref_id=%d,state=%s,depth=%u)",
|
||||
iter_type_str(reg->iter.btf, reg->iter.btf_id),
|
||||
reg->ref_obj_id, iter_state_str(reg->iter.state),
|
||||
reg->iter.depth);
|
||||
break;
|
||||
case STACK_MISC:
|
||||
case STACK_ZERO:
|
||||
default:
|
||||
reg = &state->stack[i].spilled_ptr;
|
||||
|
||||
for (j = 0; j < BPF_REG_SIZE; j++)
|
||||
types_buf[j] = slot_type_char[state->stack[i].slot_type[j]];
|
||||
types_buf[BPF_REG_SIZE] = 0;
|
||||
|
||||
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
|
||||
print_liveness(env, reg->live);
|
||||
verbose(env, "=%s", types_buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (state->acquired_refs && state->refs[0].id) {
|
||||
verbose(env, " refs=%d", state->refs[0].id);
|
||||
for (i = 1; i < state->acquired_refs; i++)
|
||||
if (state->refs[i].id)
|
||||
verbose(env, ",%d", state->refs[i].id);
|
||||
}
|
||||
if (state->in_callback_fn)
|
||||
verbose(env, " cb");
|
||||
if (state->in_async_callback_fn)
|
||||
verbose(env, " async_cb");
|
||||
verbose(env, "\n");
|
||||
if (!print_all)
|
||||
mark_verifier_state_clean(env);
|
||||
}
|
||||
|
||||
static inline u32 vlog_alignment(u32 pos)
|
||||
{
|
||||
return round_up(max(pos + BPF_LOG_MIN_ALIGNMENT / 2, BPF_LOG_ALIGNMENT),
|
||||
BPF_LOG_MIN_ALIGNMENT) - pos - 1;
|
||||
}
|
||||
|
||||
static void print_insn_state(struct bpf_verifier_env *env,
|
||||
const struct bpf_func_state *state)
|
||||
{
|
||||
if (env->prev_log_pos && env->prev_log_pos == env->log.end_pos) {
|
||||
/* remove new line character */
|
||||
bpf_vlog_reset(&env->log, env->prev_log_pos - 1);
|
||||
verbose(env, "%*c;", vlog_alignment(env->prev_insn_print_pos), ' ');
|
||||
} else {
|
||||
verbose(env, "%d:", env->insn_idx);
|
||||
}
|
||||
print_verifier_state(env, state, false);
|
||||
}
|
||||
|
||||
/* copy array src of length n * size bytes to dst. dst is reallocated if it's too
|
||||
* small to hold src. This is different from krealloc since we don't want to preserve
|
||||
* the contents of dst.
|
||||
|
||||
Reference in New Issue
Block a user