Skip to content

Commit b282131

Browse files
author
Alexei Starovoitov
committed
Merge branch 'bpf-add-bitwise-tracking-for-bpf_end'
Tianci Cao says: ==================== bpf: Add bitwise tracking for BPF_END Add bitwise tracking (tnum analysis) for BPF_END (`bswap(16|32|64)`, `be(16|32|64)`, `le(16|32|64)`) operations. Please see commit log of 1/2 for more details. v3: - Resend to fix a version control error in v2. - The rest of the changes are identical to v2. v2 (incorrect): https://lore.kernel.org/bpf/20260204091146.52447-1-ziye@zju.edu.cn/ - Refactored selftests using BSWAP_RANGE_TEST macro to eliminate code duplication and improve maintainability. (Eduard) - Simplified test names. (Eduard) - Reduced excessive comments in test cases. (Eduard) - Added more comments to explain BPF_END's special handling of zext_32_to_64. v1: https://lore.kernel.org/bpf/20260202133536.66207-1-ziye@zju.edu.cn/ ==================== Link: https://patch.msgid.link/20260204111503.77871-1-ziye@zju.edu.cn Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2 parents 4af5266 + 5641536 commit b282131

4 files changed

Lines changed: 121 additions & 3 deletions

File tree

include/linux/tnum.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ struct tnum tnum_union(struct tnum t1, struct tnum t2);
6363
/* Return @a with all but the lowest @size bytes cleared */
6464
struct tnum tnum_cast(struct tnum a, u8 size);
6565

66+
/* Swap the bytes of a tnum */
67+
struct tnum tnum_bswap16(struct tnum a);
68+
struct tnum tnum_bswap32(struct tnum a);
69+
struct tnum tnum_bswap64(struct tnum a);
70+
6671
/* Returns true if @a is a known constant */
6772
static inline bool tnum_is_const(struct tnum a)
6873
{

kernel/bpf/tnum.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99
#include <linux/kernel.h>
1010
#include <linux/tnum.h>
11+
#include <linux/swab.h>
1112

1213
#define TNUM(_v, _m) (struct tnum){.value = _v, .mask = _m}
1314
/* A completely unknown value */
@@ -253,3 +254,18 @@ struct tnum tnum_const_subreg(struct tnum a, u32 value)
253254
{
254255
return tnum_with_subreg(a, tnum_const(value));
255256
}
257+
258+
struct tnum tnum_bswap16(struct tnum a)
259+
{
260+
return TNUM(swab16(a.value & 0xFFFF), swab16(a.mask & 0xFFFF));
261+
}
262+
263+
struct tnum tnum_bswap32(struct tnum a)
264+
{
265+
return TNUM(swab32(a.value & 0xFFFFFFFF), swab32(a.mask & 0xFFFFFFFF));
266+
}
267+
268+
struct tnum tnum_bswap64(struct tnum a)
269+
{
270+
return TNUM(swab64(a.value), swab64(a.mask));
271+
}

kernel/bpf/verifier.c

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15832,6 +15832,48 @@ static void scalar_min_max_arsh(struct bpf_reg_state *dst_reg,
1583215832
__update_reg_bounds(dst_reg);
1583315833
}
1583415834

15835+
static void scalar_byte_swap(struct bpf_reg_state *dst_reg, struct bpf_insn *insn)
15836+
{
15837+
/*
15838+
* Byte swap operation - update var_off using tnum_bswap.
15839+
* Three cases:
15840+
* 1. bswap(16|32|64): opcode=0xd7 (BPF_END | BPF_ALU64 | BPF_TO_LE)
15841+
* unconditional swap
15842+
* 2. to_le(16|32|64): opcode=0xd4 (BPF_END | BPF_ALU | BPF_TO_LE)
15843+
* swap on big-endian, truncation or no-op on little-endian
15844+
* 3. to_be(16|32|64): opcode=0xdc (BPF_END | BPF_ALU | BPF_TO_BE)
15845+
* swap on little-endian, truncation or no-op on big-endian
15846+
*/
15847+
15848+
bool alu64 = BPF_CLASS(insn->code) == BPF_ALU64;
15849+
bool to_le = BPF_SRC(insn->code) == BPF_TO_LE;
15850+
bool is_big_endian;
15851+
#ifdef CONFIG_CPU_BIG_ENDIAN
15852+
is_big_endian = true;
15853+
#else
15854+
is_big_endian = false;
15855+
#endif
15856+
/* Apply bswap if alu64 or switch between big-endian and little-endian machines */
15857+
bool need_bswap = alu64 || (to_le == is_big_endian);
15858+
15859+
if (need_bswap) {
15860+
if (insn->imm == 16)
15861+
dst_reg->var_off = tnum_bswap16(dst_reg->var_off);
15862+
else if (insn->imm == 32)
15863+
dst_reg->var_off = tnum_bswap32(dst_reg->var_off);
15864+
else if (insn->imm == 64)
15865+
dst_reg->var_off = tnum_bswap64(dst_reg->var_off);
15866+
/*
15867+
* Byteswap scrambles the range, so we must reset bounds.
15868+
* Bounds will be re-derived from the new tnum later.
15869+
*/
15870+
__mark_reg_unbounded(dst_reg);
15871+
}
15872+
/* For bswap16/32, truncate dst register to match the swapped size */
15873+
if (insn->imm == 16 || insn->imm == 32)
15874+
coerce_reg_to_size(dst_reg, insn->imm / 8);
15875+
}
15876+
1583515877
static bool is_safe_to_compute_dst_reg_range(struct bpf_insn *insn,
1583615878
const struct bpf_reg_state *src_reg)
1583715879
{
@@ -15858,6 +15900,7 @@ static bool is_safe_to_compute_dst_reg_range(struct bpf_insn *insn,
1585815900
case BPF_XOR:
1585915901
case BPF_OR:
1586015902
case BPF_MUL:
15903+
case BPF_END:
1586115904
return true;
1586215905

1586315906
/*
@@ -16047,12 +16090,23 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
1604716090
else
1604816091
scalar_min_max_arsh(dst_reg, &src_reg);
1604916092
break;
16093+
case BPF_END:
16094+
scalar_byte_swap(dst_reg, insn);
16095+
break;
1605016096
default:
1605116097
break;
1605216098
}
1605316099

16054-
/* ALU32 ops are zero extended into 64bit register */
16055-
if (alu32)
16100+
/*
16101+
* ALU32 ops are zero extended into 64bit register.
16102+
*
16103+
* BPF_END is already handled inside the helper (truncation),
16104+
* so skip zext here to avoid unexpected zero extension.
16105+
* e.g., le64: opcode=(BPF_END|BPF_ALU|BPF_TO_LE), imm=0x40
16106+
* This is a 64bit byte swap operation with alu32==true,
16107+
* but we should not zero extend the result.
16108+
*/
16109+
if (alu32 && opcode != BPF_END)
1605616110
zext_32_to_64(dst_reg);
1605716111
reg_bounds_sync(dst_reg);
1605816112
return 0;
@@ -16232,7 +16286,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
1623216286
}
1623316287

1623416288
/* check dest operand */
16235-
if (opcode == BPF_NEG &&
16289+
if ((opcode == BPF_NEG || opcode == BPF_END) &&
1623616290
regs[insn->dst_reg].type == SCALAR_VALUE) {
1623716291
err = check_reg_arg(env, insn->dst_reg, DST_OP_NO_MARK);
1623816292
err = err ?: adjust_scalar_min_max_vals(env, insn,

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,49 @@ __naked void bswap_64(void)
4848
: __clobber_all);
4949
}
5050

51+
#define BSWAP_RANGE_TEST(name, op, in_value, out_value) \
52+
SEC("socket") \
53+
__success __log_level(2) \
54+
__msg("r0 &= {{.*}}; R0=scalar({{.*}},var_off=(0x0; " #in_value "))") \
55+
__msg("r0 = " op " r0 {{.*}}; R0=scalar({{.*}},var_off=(0x0; " #out_value "))") \
56+
__naked void name(void) \
57+
{ \
58+
asm volatile ( \
59+
"call %[bpf_get_prandom_u32];" \
60+
"r0 &= " #in_value ";" \
61+
"r0 = " op " r0;" \
62+
"r2 = " #out_value " ll;" \
63+
"if r0 > r2 goto trap_%=;" \
64+
"r0 = 0;" \
65+
"exit;" \
66+
"trap_%=:" \
67+
"r1 = 42;" \
68+
"r0 = *(u64 *)(r1 + 0);" \
69+
"exit;" \
70+
: \
71+
: __imm(bpf_get_prandom_u32) \
72+
: __clobber_all); \
73+
}
74+
75+
BSWAP_RANGE_TEST(bswap16_range, "bswap16", 0x3f00, 0x3f)
76+
BSWAP_RANGE_TEST(bswap32_range, "bswap32", 0x3f00, 0x3f0000)
77+
BSWAP_RANGE_TEST(bswap64_range, "bswap64", 0x3f00, 0x3f000000000000)
78+
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
79+
BSWAP_RANGE_TEST(be16_range, "be16", 0x3f00, 0x3f)
80+
BSWAP_RANGE_TEST(be32_range, "be32", 0x3f00, 0x3f0000)
81+
BSWAP_RANGE_TEST(be64_range, "be64", 0x3f00, 0x3f000000000000)
82+
BSWAP_RANGE_TEST(le16_range, "le16", 0x3f00, 0x3f00)
83+
BSWAP_RANGE_TEST(le32_range, "le32", 0x3f00, 0x3f00)
84+
BSWAP_RANGE_TEST(le64_range, "le64", 0x3f00, 0x3f00)
85+
#else
86+
BSWAP_RANGE_TEST(be16_range, "be16", 0x3f00, 0x3f00)
87+
BSWAP_RANGE_TEST(be32_range, "be32", 0x3f00, 0x3f00)
88+
BSWAP_RANGE_TEST(be64_range, "be64", 0x3f00, 0x3f00)
89+
BSWAP_RANGE_TEST(le16_range, "le16", 0x3f00, 0x3f)
90+
BSWAP_RANGE_TEST(le32_range, "le32", 0x3f00, 0x3f0000)
91+
BSWAP_RANGE_TEST(le64_range, "le64", 0x3f00, 0x3f000000000000)
92+
#endif
93+
5194
#else
5295

5396
SEC("socket")

0 commit comments

Comments
 (0)