mirror of
https://github.com/torvalds/linux.git
synced 2026-05-29 17:43:52 +02:00
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>
This commit is contained in:
commit
b2821311ab
|
|
@ -63,6 +63,11 @@ struct tnum tnum_union(struct tnum t1, struct tnum t2);
|
|||
/* Return @a with all but the lowest @size bytes cleared */
|
||||
struct tnum tnum_cast(struct tnum a, u8 size);
|
||||
|
||||
/* Swap the bytes of a tnum */
|
||||
struct tnum tnum_bswap16(struct tnum a);
|
||||
struct tnum tnum_bswap32(struct tnum a);
|
||||
struct tnum tnum_bswap64(struct tnum a);
|
||||
|
||||
/* Returns true if @a is a known constant */
|
||||
static inline bool tnum_is_const(struct tnum a)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/tnum.h>
|
||||
#include <linux/swab.h>
|
||||
|
||||
#define TNUM(_v, _m) (struct tnum){.value = _v, .mask = _m}
|
||||
/* A completely unknown value */
|
||||
|
|
@ -253,3 +254,18 @@ struct tnum tnum_const_subreg(struct tnum a, u32 value)
|
|||
{
|
||||
return tnum_with_subreg(a, tnum_const(value));
|
||||
}
|
||||
|
||||
struct tnum tnum_bswap16(struct tnum a)
|
||||
{
|
||||
return TNUM(swab16(a.value & 0xFFFF), swab16(a.mask & 0xFFFF));
|
||||
}
|
||||
|
||||
struct tnum tnum_bswap32(struct tnum a)
|
||||
{
|
||||
return TNUM(swab32(a.value & 0xFFFFFFFF), swab32(a.mask & 0xFFFFFFFF));
|
||||
}
|
||||
|
||||
struct tnum tnum_bswap64(struct tnum a)
|
||||
{
|
||||
return TNUM(swab64(a.value), swab64(a.mask));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15832,6 +15832,48 @@ static void scalar_min_max_arsh(struct bpf_reg_state *dst_reg,
|
|||
__update_reg_bounds(dst_reg);
|
||||
}
|
||||
|
||||
static void scalar_byte_swap(struct bpf_reg_state *dst_reg, struct bpf_insn *insn)
|
||||
{
|
||||
/*
|
||||
* Byte swap operation - update var_off using tnum_bswap.
|
||||
* Three cases:
|
||||
* 1. bswap(16|32|64): opcode=0xd7 (BPF_END | BPF_ALU64 | BPF_TO_LE)
|
||||
* unconditional swap
|
||||
* 2. to_le(16|32|64): opcode=0xd4 (BPF_END | BPF_ALU | BPF_TO_LE)
|
||||
* swap on big-endian, truncation or no-op on little-endian
|
||||
* 3. to_be(16|32|64): opcode=0xdc (BPF_END | BPF_ALU | BPF_TO_BE)
|
||||
* swap on little-endian, truncation or no-op on big-endian
|
||||
*/
|
||||
|
||||
bool alu64 = BPF_CLASS(insn->code) == BPF_ALU64;
|
||||
bool to_le = BPF_SRC(insn->code) == BPF_TO_LE;
|
||||
bool is_big_endian;
|
||||
#ifdef CONFIG_CPU_BIG_ENDIAN
|
||||
is_big_endian = true;
|
||||
#else
|
||||
is_big_endian = false;
|
||||
#endif
|
||||
/* Apply bswap if alu64 or switch between big-endian and little-endian machines */
|
||||
bool need_bswap = alu64 || (to_le == is_big_endian);
|
||||
|
||||
if (need_bswap) {
|
||||
if (insn->imm == 16)
|
||||
dst_reg->var_off = tnum_bswap16(dst_reg->var_off);
|
||||
else if (insn->imm == 32)
|
||||
dst_reg->var_off = tnum_bswap32(dst_reg->var_off);
|
||||
else if (insn->imm == 64)
|
||||
dst_reg->var_off = tnum_bswap64(dst_reg->var_off);
|
||||
/*
|
||||
* Byteswap scrambles the range, so we must reset bounds.
|
||||
* Bounds will be re-derived from the new tnum later.
|
||||
*/
|
||||
__mark_reg_unbounded(dst_reg);
|
||||
}
|
||||
/* For bswap16/32, truncate dst register to match the swapped size */
|
||||
if (insn->imm == 16 || insn->imm == 32)
|
||||
coerce_reg_to_size(dst_reg, insn->imm / 8);
|
||||
}
|
||||
|
||||
static bool is_safe_to_compute_dst_reg_range(struct bpf_insn *insn,
|
||||
const struct bpf_reg_state *src_reg)
|
||||
{
|
||||
|
|
@ -15858,6 +15900,7 @@ static bool is_safe_to_compute_dst_reg_range(struct bpf_insn *insn,
|
|||
case BPF_XOR:
|
||||
case BPF_OR:
|
||||
case BPF_MUL:
|
||||
case BPF_END:
|
||||
return true;
|
||||
|
||||
/*
|
||||
|
|
@ -16047,12 +16090,23 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
|
|||
else
|
||||
scalar_min_max_arsh(dst_reg, &src_reg);
|
||||
break;
|
||||
case BPF_END:
|
||||
scalar_byte_swap(dst_reg, insn);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* ALU32 ops are zero extended into 64bit register */
|
||||
if (alu32)
|
||||
/*
|
||||
* ALU32 ops are zero extended into 64bit register.
|
||||
*
|
||||
* BPF_END is already handled inside the helper (truncation),
|
||||
* so skip zext here to avoid unexpected zero extension.
|
||||
* e.g., le64: opcode=(BPF_END|BPF_ALU|BPF_TO_LE), imm=0x40
|
||||
* This is a 64bit byte swap operation with alu32==true,
|
||||
* but we should not zero extend the result.
|
||||
*/
|
||||
if (alu32 && opcode != BPF_END)
|
||||
zext_32_to_64(dst_reg);
|
||||
reg_bounds_sync(dst_reg);
|
||||
return 0;
|
||||
|
|
@ -16232,7 +16286,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
|
|||
}
|
||||
|
||||
/* check dest operand */
|
||||
if (opcode == BPF_NEG &&
|
||||
if ((opcode == BPF_NEG || opcode == BPF_END) &&
|
||||
regs[insn->dst_reg].type == SCALAR_VALUE) {
|
||||
err = check_reg_arg(env, insn->dst_reg, DST_OP_NO_MARK);
|
||||
err = err ?: adjust_scalar_min_max_vals(env, insn,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,49 @@ __naked void bswap_64(void)
|
|||
: __clobber_all);
|
||||
}
|
||||
|
||||
#define BSWAP_RANGE_TEST(name, op, in_value, out_value) \
|
||||
SEC("socket") \
|
||||
__success __log_level(2) \
|
||||
__msg("r0 &= {{.*}}; R0=scalar({{.*}},var_off=(0x0; " #in_value "))") \
|
||||
__msg("r0 = " op " r0 {{.*}}; R0=scalar({{.*}},var_off=(0x0; " #out_value "))") \
|
||||
__naked void name(void) \
|
||||
{ \
|
||||
asm volatile ( \
|
||||
"call %[bpf_get_prandom_u32];" \
|
||||
"r0 &= " #in_value ";" \
|
||||
"r0 = " op " r0;" \
|
||||
"r2 = " #out_value " ll;" \
|
||||
"if r0 > r2 goto trap_%=;" \
|
||||
"r0 = 0;" \
|
||||
"exit;" \
|
||||
"trap_%=:" \
|
||||
"r1 = 42;" \
|
||||
"r0 = *(u64 *)(r1 + 0);" \
|
||||
"exit;" \
|
||||
: \
|
||||
: __imm(bpf_get_prandom_u32) \
|
||||
: __clobber_all); \
|
||||
}
|
||||
|
||||
BSWAP_RANGE_TEST(bswap16_range, "bswap16", 0x3f00, 0x3f)
|
||||
BSWAP_RANGE_TEST(bswap32_range, "bswap32", 0x3f00, 0x3f0000)
|
||||
BSWAP_RANGE_TEST(bswap64_range, "bswap64", 0x3f00, 0x3f000000000000)
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
BSWAP_RANGE_TEST(be16_range, "be16", 0x3f00, 0x3f)
|
||||
BSWAP_RANGE_TEST(be32_range, "be32", 0x3f00, 0x3f0000)
|
||||
BSWAP_RANGE_TEST(be64_range, "be64", 0x3f00, 0x3f000000000000)
|
||||
BSWAP_RANGE_TEST(le16_range, "le16", 0x3f00, 0x3f00)
|
||||
BSWAP_RANGE_TEST(le32_range, "le32", 0x3f00, 0x3f00)
|
||||
BSWAP_RANGE_TEST(le64_range, "le64", 0x3f00, 0x3f00)
|
||||
#else
|
||||
BSWAP_RANGE_TEST(be16_range, "be16", 0x3f00, 0x3f00)
|
||||
BSWAP_RANGE_TEST(be32_range, "be32", 0x3f00, 0x3f00)
|
||||
BSWAP_RANGE_TEST(be64_range, "be64", 0x3f00, 0x3f00)
|
||||
BSWAP_RANGE_TEST(le16_range, "le16", 0x3f00, 0x3f)
|
||||
BSWAP_RANGE_TEST(le32_range, "le32", 0x3f00, 0x3f0000)
|
||||
BSWAP_RANGE_TEST(le64_range, "le64", 0x3f00, 0x3f000000000000)
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
SEC("socket")
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user