Skip to content

Commit 72f8a1d

Browse files
anakryikoborkmann
authored andcommitted
bpf: Disambiguate SCALAR register state output in verifier logs
Currently the way that verifier prints SCALAR_VALUE register state (and PTR_TO_PACKET, which can have var_off and ranges info as well) is very ambiguous. In the name of brevity we are trying to eliminate "unnecessary" output of umin/umax, smin/smax, u32_min/u32_max, and s32_min/s32_max values, if possible. Current rules are that if any of those have their default value (which for mins is the minimal value of its respective types: 0, S32_MIN, or S64_MIN, while for maxs it's U32_MAX, S32_MAX, S64_MAX, or U64_MAX) *OR* if there is another min/max value that as matching value. E.g., if smin=100 and umin=100, we'll emit only umin=10, omitting smin altogether. This approach has a few problems, being both ambiguous and sort-of incorrect in some cases. Ambiguity is due to missing value could be either default value or value of umin/umax or smin/smax. This is especially confusing when we mix signed and unsigned ranges. Quite often, umin=0 and smin=0, and so we'll have only `umin=0` leaving anyone reading verifier log to guess whether smin is actually 0 or it's actually -9223372036854775808 (S64_MIN). And often times it's important to know, especially when debugging tricky issues. "Sort-of incorrectness" comes from mixing negative and positive values. E.g., if umin is some large positive number, it can be equal to smin which is, interpreted as signed value, is actually some negative value. Currently, that smin will be omitted and only umin will be emitted with a large positive value, giving an impression that smin is also positive. Anyway, ambiguity is the biggest issue making it impossible to have an exact understanding of register state, preventing any sort of automated testing of verifier state based on verifier log. This patch is attempting to rectify the situation by removing ambiguity, while minimizing the verboseness of register state output. The rules are straightforward: - if some of the values are missing, then it definitely has a default value. I.e., `umin=0` means that umin is zero, but smin is actually S64_MIN; - all the various boundaries that happen to have the same value are emitted in one equality separated sequence. E.g., if umin and smin are both 100, we'll emit `smin=umin=100`, making this explicit; - we do not mix negative and positive values together, and even if they happen to have the same bit-level value, they will be emitted separately with proper sign. I.e., if both umax and smax happen to be 0xffffffffffffffff, we'll emit them both separately as `smax=-1,umax=18446744073709551615`; - in the name of a bit more uniformity and consistency, {u32,s32}_{min,max} are renamed to {s,u}{min,max}32, which seems to improve readability. The above means that in case of all 4 ranges being, say, [50, 100] range, we'd previously see hugely ambiguous: R1=scalar(umin=50,umax=100) Now, we'll be more explicit: R1=scalar(smin=umin=smin32=umin32=50,smax=umax=smax32=umax32=100) This is slightly more verbose, but distinct from the case when we don't know anything about signed boundaries and 32-bit boundaries, which under new rules will match the old case: R1=scalar(umin=50,umax=100) Also, in the name of simplicity of implementation and consistency, order for {s,u}32_{min,max} are emitted *before* var_off. Previously they were emitted afterwards, for unclear reasons. This patch also includes a few fixes to selftests that expect exact register state to accommodate slight changes to verifier format. You can see that the changes are pretty minimal in common cases. Note, the special case when SCALAR_VALUE register is a known constant isn't changed, we'll emit constant value once, interpreted as signed value. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> Acked-by: John Fastabend <john.fastabend@gmail.com> Acked-by: Eduard Zingerman <eddyz87@gmail.com> Link: https://lore.kernel.org/bpf/20231011223728.3188086-5-andrii@kernel.org
1 parent cde7851 commit 72f8a1d

File tree

3 files changed

+55
-32
lines changed

3 files changed

+55
-32
lines changed

kernel/bpf/verifier.c

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,50 @@ static void scrub_spilled_slot(u8 *stype)
13421342
*stype = STACK_MISC;
13431343
}
13441344

1345+
static void print_scalar_ranges(struct bpf_verifier_env *env,
1346+
const struct bpf_reg_state *reg,
1347+
const char **sep)
1348+
{
1349+
struct {
1350+
const char *name;
1351+
u64 val;
1352+
bool omit;
1353+
} minmaxs[] = {
1354+
{"smin", reg->smin_value, reg->smin_value == S64_MIN},
1355+
{"smax", reg->smax_value, reg->smax_value == S64_MAX},
1356+
{"umin", reg->umin_value, reg->umin_value == 0},
1357+
{"umax", reg->umax_value, reg->umax_value == U64_MAX},
1358+
{"smin32", (s64)reg->s32_min_value, reg->s32_min_value == S32_MIN},
1359+
{"smax32", (s64)reg->s32_max_value, reg->s32_max_value == S32_MAX},
1360+
{"umin32", reg->u32_min_value, reg->u32_min_value == 0},
1361+
{"umax32", reg->u32_max_value, reg->u32_max_value == U32_MAX},
1362+
}, *m1, *m2, *mend = &minmaxs[ARRAY_SIZE(minmaxs)];
1363+
bool neg1, neg2;
1364+
1365+
for (m1 = &minmaxs[0]; m1 < mend; m1++) {
1366+
if (m1->omit)
1367+
continue;
1368+
1369+
neg1 = m1->name[0] == 's' && (s64)m1->val < 0;
1370+
1371+
verbose(env, "%s%s=", *sep, m1->name);
1372+
*sep = ",";
1373+
1374+
for (m2 = m1 + 2; m2 < mend; m2 += 2) {
1375+
if (m2->omit || m2->val != m1->val)
1376+
continue;
1377+
/* don't mix negatives with positives */
1378+
neg2 = m2->name[0] == 's' && (s64)m2->val < 0;
1379+
if (neg2 != neg1)
1380+
continue;
1381+
m2->omit = true;
1382+
verbose(env, "%s=", m2->name);
1383+
}
1384+
1385+
verbose(env, m1->name[0] == 's' ? "%lld" : "%llu", m1->val);
1386+
}
1387+
}
1388+
13451389
static void print_verifier_state(struct bpf_verifier_env *env,
13461390
const struct bpf_func_state *state,
13471391
bool print_all)
@@ -1405,34 +1449,13 @@ static void print_verifier_state(struct bpf_verifier_env *env,
14051449
*/
14061450
verbose_a("imm=%llx", reg->var_off.value);
14071451
} else {
1408-
if (reg->smin_value != reg->umin_value &&
1409-
reg->smin_value != S64_MIN)
1410-
verbose_a("smin=%lld", (long long)reg->smin_value);
1411-
if (reg->smax_value != reg->umax_value &&
1412-
reg->smax_value != S64_MAX)
1413-
verbose_a("smax=%lld", (long long)reg->smax_value);
1414-
if (reg->umin_value != 0)
1415-
verbose_a("umin=%llu", (unsigned long long)reg->umin_value);
1416-
if (reg->umax_value != U64_MAX)
1417-
verbose_a("umax=%llu", (unsigned long long)reg->umax_value);
1452+
print_scalar_ranges(env, reg, &sep);
14181453
if (!tnum_is_unknown(reg->var_off)) {
14191454
char tn_buf[48];
14201455

14211456
tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
14221457
verbose_a("var_off=%s", tn_buf);
14231458
}
1424-
if (reg->s32_min_value != reg->smin_value &&
1425-
reg->s32_min_value != S32_MIN)
1426-
verbose_a("s32_min=%d", (int)(reg->s32_min_value));
1427-
if (reg->s32_max_value != reg->smax_value &&
1428-
reg->s32_max_value != S32_MAX)
1429-
verbose_a("s32_max=%d", (int)(reg->s32_max_value));
1430-
if (reg->u32_min_value != reg->umin_value &&
1431-
reg->u32_min_value != U32_MIN)
1432-
verbose_a("u32_min=%d", (int)(reg->u32_min_value));
1433-
if (reg->u32_max_value != reg->umax_value &&
1434-
reg->u32_max_value != U32_MAX)
1435-
verbose_a("u32_max=%d", (int)(reg->u32_max_value));
14361459
}
14371460
#undef verbose_a
14381461

tools/testing/selftests/bpf/progs/exceptions_assert.c

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,35 +31,35 @@ check_assert(s64, eq, llong_max, LLONG_MAX);
3131

3232
__msg(": R0_w=scalar(smax=2147483646) R10=fp0")
3333
check_assert(s64, lt, pos, INT_MAX);
34-
__msg(": R0_w=scalar(umin=9223372036854775808,var_off=(0x8000000000000000; 0x7fffffffffffffff))")
34+
__msg(": R0_w=scalar(smax=-1,umin=9223372036854775808,var_off=(0x8000000000000000; 0x7fffffffffffffff))")
3535
check_assert(s64, lt, zero, 0);
36-
__msg(": R0_w=scalar(umin=9223372036854775808,umax=18446744071562067967,var_off=(0x8000000000000000; 0x7fffffffffffffff))")
36+
__msg(": R0_w=scalar(smax=-2147483649,umin=9223372036854775808,umax=18446744071562067967,var_off=(0x8000000000000000; 0x7fffffffffffffff))")
3737
check_assert(s64, lt, neg, INT_MIN);
3838

3939
__msg(": R0_w=scalar(smax=2147483647) R10=fp0")
4040
check_assert(s64, le, pos, INT_MAX);
4141
__msg(": R0_w=scalar(smax=0) R10=fp0")
4242
check_assert(s64, le, zero, 0);
43-
__msg(": R0_w=scalar(umin=9223372036854775808,umax=18446744071562067968,var_off=(0x8000000000000000; 0x7fffffffffffffff))")
43+
__msg(": R0_w=scalar(smax=-2147483648,umin=9223372036854775808,umax=18446744071562067968,var_off=(0x8000000000000000; 0x7fffffffffffffff))")
4444
check_assert(s64, le, neg, INT_MIN);
4545

46-
__msg(": R0_w=scalar(umin=2147483648,umax=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff))")
46+
__msg(": R0_w=scalar(smin=umin=2147483648,umax=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff))")
4747
check_assert(s64, gt, pos, INT_MAX);
48-
__msg(": R0_w=scalar(umin=1,umax=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff))")
48+
__msg(": R0_w=scalar(smin=umin=1,umax=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff))")
4949
check_assert(s64, gt, zero, 0);
5050
__msg(": R0_w=scalar(smin=-2147483647) R10=fp0")
5151
check_assert(s64, gt, neg, INT_MIN);
5252

53-
__msg(": R0_w=scalar(umin=2147483647,umax=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff))")
53+
__msg(": R0_w=scalar(smin=umin=2147483647,umax=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff))")
5454
check_assert(s64, ge, pos, INT_MAX);
55-
__msg(": R0_w=scalar(umax=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff)) R10=fp0")
55+
__msg(": R0_w=scalar(smin=0,umax=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff)) R10=fp0")
5656
check_assert(s64, ge, zero, 0);
5757
__msg(": R0_w=scalar(smin=-2147483648) R10=fp0")
5858
check_assert(s64, ge, neg, INT_MIN);
5959

6060
SEC("?tc")
6161
__log_level(2) __failure
62-
__msg(": R0=0 R1=ctx(off=0,imm=0) R2=scalar(smin=-2147483646,smax=2147483645) R10=fp0")
62+
__msg(": R0=0 R1=ctx(off=0,imm=0) R2=scalar(smin=smin32=-2147483646,smax=smax32=2147483645) R10=fp0")
6363
int check_assert_range_s64(struct __sk_buff *ctx)
6464
{
6565
struct bpf_sock *sk = ctx->sk;
@@ -75,7 +75,7 @@ int check_assert_range_s64(struct __sk_buff *ctx)
7575

7676
SEC("?tc")
7777
__log_level(2) __failure
78-
__msg(": R1=ctx(off=0,imm=0) R2=scalar(umin=4096,umax=8192,var_off=(0x0; 0x3fff))")
78+
__msg(": R1=ctx(off=0,imm=0) R2=scalar(smin=umin=smin32=umin32=4096,smax=umax=smax32=umax32=8192,var_off=(0x0; 0x3fff))")
7979
int check_assert_range_u64(struct __sk_buff *ctx)
8080
{
8181
u64 num = ctx->len;

tools/testing/selftests/bpf/progs/verifier_ldsx.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ __naked void ldsx_s32(void)
6464
SEC("socket")
6565
__description("LDSX, S8 range checking, privileged")
6666
__log_level(2) __success __retval(1)
67-
__msg("R1_w=scalar(smin=-128,smax=127)")
67+
__msg("R1_w=scalar(smin=smin32=-128,smax=smax32=127)")
6868
__naked void ldsx_s8_range_priv(void)
6969
{
7070
asm volatile (

0 commit comments

Comments
 (0)