Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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>

+121 -3
+5
include/linux/tnum.h
··· 63 63 /* Return @a with all but the lowest @size bytes cleared */ 64 64 struct tnum tnum_cast(struct tnum a, u8 size); 65 65 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 + 66 71 /* Returns true if @a is a known constant */ 67 72 static inline bool tnum_is_const(struct tnum a) 68 73 {
+16
kernel/bpf/tnum.c
··· 8 8 */ 9 9 #include <linux/kernel.h> 10 10 #include <linux/tnum.h> 11 + #include <linux/swab.h> 11 12 12 13 #define TNUM(_v, _m) (struct tnum){.value = _v, .mask = _m} 13 14 /* A completely unknown value */ ··· 253 252 struct tnum tnum_const_subreg(struct tnum a, u32 value) 254 253 { 255 254 return tnum_with_subreg(a, tnum_const(value)); 255 + } 256 + 257 + struct tnum tnum_bswap16(struct tnum a) 258 + { 259 + return TNUM(swab16(a.value & 0xFFFF), swab16(a.mask & 0xFFFF)); 260 + } 261 + 262 + struct tnum tnum_bswap32(struct tnum a) 263 + { 264 + return TNUM(swab32(a.value & 0xFFFFFFFF), swab32(a.mask & 0xFFFFFFFF)); 265 + } 266 + 267 + struct tnum tnum_bswap64(struct tnum a) 268 + { 269 + return TNUM(swab64(a.value), swab64(a.mask)); 256 270 }
+57 -3
kernel/bpf/verifier.c
··· 15832 15832 __update_reg_bounds(dst_reg); 15833 15833 } 15834 15834 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 + 15835 15877 static bool is_safe_to_compute_dst_reg_range(struct bpf_insn *insn, 15836 15878 const struct bpf_reg_state *src_reg) 15837 15879 { ··· 15900 15858 case BPF_XOR: 15901 15859 case BPF_OR: 15902 15860 case BPF_MUL: 15861 + case BPF_END: 15903 15862 return true; 15904 15863 15905 15864 /* ··· 16090 16047 else 16091 16048 scalar_min_max_arsh(dst_reg, &src_reg); 16092 16049 break; 16050 + case BPF_END: 16051 + scalar_byte_swap(dst_reg, insn); 16052 + break; 16093 16053 default: 16094 16054 break; 16095 16055 } 16096 16056 16097 - /* ALU32 ops are zero extended into 64bit register */ 16098 - if (alu32) 16057 + /* 16058 + * ALU32 ops are zero extended into 64bit register. 16059 + * 16060 + * BPF_END is already handled inside the helper (truncation), 16061 + * so skip zext here to avoid unexpected zero extension. 16062 + * e.g., le64: opcode=(BPF_END|BPF_ALU|BPF_TO_LE), imm=0x40 16063 + * This is a 64bit byte swap operation with alu32==true, 16064 + * but we should not zero extend the result. 16065 + */ 16066 + if (alu32 && opcode != BPF_END) 16099 16067 zext_32_to_64(dst_reg); 16100 16068 reg_bounds_sync(dst_reg); 16101 16069 return 0; ··· 16286 16232 } 16287 16233 16288 16234 /* check dest operand */ 16289 - if (opcode == BPF_NEG && 16235 + if ((opcode == BPF_NEG || opcode == BPF_END) && 16290 16236 regs[insn->dst_reg].type == SCALAR_VALUE) { 16291 16237 err = check_reg_arg(env, insn->dst_reg, DST_OP_NO_MARK); 16292 16238 err = err ?: adjust_scalar_min_max_vals(env, insn,
+43
tools/testing/selftests/bpf/progs/verifier_bswap.c
··· 48 48 : __clobber_all); 49 49 } 50 50 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 + 51 94 #else 52 95 53 96 SEC("socket")