mirror of
https://github.com/torvalds/linux.git
synced 2026-06-02 19:43:40 +02:00
Merge branch 'bpf-add-range-tracking-for-bpf_div-and-bpf_mod'
Yazhou Tang says: ==================== bpf: Add range tracking for BPF_DIV and BPF_MOD From: Yazhou Tang <tangyazhou518@outlook.com> Add range tracking (interval analysis) for BPF_DIV and BPF_MOD when divisor is constant. Please see commit log of 1/2 for more details. Changes v4 => v5: 1. Rename helper functions `__reset_reg(32|64)_and_tnum` to `reset_reg(32|64)_and_tnum`. (Alexei) 2. Replace plain C division with `div64_u64` and `div64_s64` for 64-bit operations, ensuring compatibility with 32-bit architectures. (Alexei & kernel test robot) 3. Fixup an indent typo in selftest file `verifier_div_mod_bounds.c`. v4: https://lore.kernel.org/bpf/20260116103246.2477635-1-tangyazhou@zju.edu.cn/ Changes v3 => v4: 1. Remove verbose helper functions for "division by zero" handling. (Alexei) 2. Put all "reset" logic in one place for clarity, and add 2 helper function `__reset_reg64_and_tnum` and `__reset_reg32_and_tnum` to reduce code duplication. (Alexei) 3. Update all multi-line comments to follow the standard kernel style. (Alexei) 4. Add new test cases to cover strictly positive and strictly negative divisor scenarios in SDIV and SMOD analysis. (Alexei) 5. Fixup a typo in SDIV analysis functions. v3: https://lore.kernel.org/bpf/20260113103552.3435695-1-tangyazhou@zju.edu.cn/ Changes v2 => v3: 1. Fixup a bug in `adjust_scalar_min_max_vals` function that lead to incorrect range results. (Syzbot) 2. Remove tnum analysis logic. (Alexei) 3. Only handle "constant divisor" case. (Alexei) 4. Add BPF_MOD range analysis logic. 5. Update selftests accordingly. 6. Add detailed code comments and improve commit messages. (Yonghong) v2: https://lore.kernel.org/bpf/20251223091120.2413435-1-tangyazhou@zju.edu.cn/ Changes v1 => v2: 1. Fixed 2 bugs in sdiv32 analysis logic and corrected the associated selftest cases. (AI reviewer) 2. Renamed `tnum_bottom` to `tnum_empty` for better clarity, and updated commit message to explain its role in signed BPF_DIV analysis. v1: https://lore.kernel.org/bpf/tencent_717092CD734D050CCD93401CA624BB3C8307@qq.com/ https://lore.kernel.org/bpf/tencent_7C98FAECA40C98489ACF4515CE346F031509@qq.com/ ==================== Link: https://patch.msgid.link/20260119085458.182221-1-tangyazhou@zju.edu.cn Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
900dbb6db6
|
|
@ -2349,6 +2349,18 @@ static void __mark_reg32_unbounded(struct bpf_reg_state *reg)
|
|||
reg->u32_max_value = U32_MAX;
|
||||
}
|
||||
|
||||
static void reset_reg64_and_tnum(struct bpf_reg_state *reg)
|
||||
{
|
||||
__mark_reg64_unbounded(reg);
|
||||
reg->var_off = tnum_unknown;
|
||||
}
|
||||
|
||||
static void reset_reg32_and_tnum(struct bpf_reg_state *reg)
|
||||
{
|
||||
__mark_reg32_unbounded(reg);
|
||||
reg->var_off = tnum_unknown;
|
||||
}
|
||||
|
||||
static void __update_reg32_bounds(struct bpf_reg_state *reg)
|
||||
{
|
||||
struct tnum var32_off = tnum_subreg(reg->var_off);
|
||||
|
|
@ -15159,6 +15171,252 @@ static void scalar_min_max_mul(struct bpf_reg_state *dst_reg,
|
|||
}
|
||||
}
|
||||
|
||||
static void scalar32_min_max_udiv(struct bpf_reg_state *dst_reg,
|
||||
struct bpf_reg_state *src_reg)
|
||||
{
|
||||
u32 *dst_umin = &dst_reg->u32_min_value;
|
||||
u32 *dst_umax = &dst_reg->u32_max_value;
|
||||
u32 src_val = src_reg->u32_min_value; /* non-zero, const divisor */
|
||||
|
||||
*dst_umin = *dst_umin / src_val;
|
||||
*dst_umax = *dst_umax / src_val;
|
||||
|
||||
/* Reset other ranges/tnum to unbounded/unknown. */
|
||||
dst_reg->s32_min_value = S32_MIN;
|
||||
dst_reg->s32_max_value = S32_MAX;
|
||||
reset_reg64_and_tnum(dst_reg);
|
||||
}
|
||||
|
||||
static void scalar_min_max_udiv(struct bpf_reg_state *dst_reg,
|
||||
struct bpf_reg_state *src_reg)
|
||||
{
|
||||
u64 *dst_umin = &dst_reg->umin_value;
|
||||
u64 *dst_umax = &dst_reg->umax_value;
|
||||
u64 src_val = src_reg->umin_value; /* non-zero, const divisor */
|
||||
|
||||
*dst_umin = div64_u64(*dst_umin, src_val);
|
||||
*dst_umax = div64_u64(*dst_umax, src_val);
|
||||
|
||||
/* Reset other ranges/tnum to unbounded/unknown. */
|
||||
dst_reg->smin_value = S64_MIN;
|
||||
dst_reg->smax_value = S64_MAX;
|
||||
reset_reg32_and_tnum(dst_reg);
|
||||
}
|
||||
|
||||
static void scalar32_min_max_sdiv(struct bpf_reg_state *dst_reg,
|
||||
struct bpf_reg_state *src_reg)
|
||||
{
|
||||
s32 *dst_smin = &dst_reg->s32_min_value;
|
||||
s32 *dst_smax = &dst_reg->s32_max_value;
|
||||
s32 src_val = src_reg->s32_min_value; /* non-zero, const divisor */
|
||||
s32 res1, res2;
|
||||
|
||||
/* BPF div specification: S32_MIN / -1 = S32_MIN */
|
||||
if (*dst_smin == S32_MIN && src_val == -1) {
|
||||
/*
|
||||
* If the dividend range contains more than just S32_MIN,
|
||||
* we cannot precisely track the result, so it becomes unbounded.
|
||||
* e.g., [S32_MIN, S32_MIN+10]/(-1),
|
||||
* = {S32_MIN} U [-(S32_MIN+10), -(S32_MIN+1)]
|
||||
* = {S32_MIN} U [S32_MAX-9, S32_MAX] = [S32_MIN, S32_MAX]
|
||||
* Otherwise (if dividend is exactly S32_MIN), result remains S32_MIN.
|
||||
*/
|
||||
if (*dst_smax != S32_MIN) {
|
||||
*dst_smin = S32_MIN;
|
||||
*dst_smax = S32_MAX;
|
||||
}
|
||||
goto reset;
|
||||
}
|
||||
|
||||
res1 = *dst_smin / src_val;
|
||||
res2 = *dst_smax / src_val;
|
||||
*dst_smin = min(res1, res2);
|
||||
*dst_smax = max(res1, res2);
|
||||
|
||||
reset:
|
||||
/* Reset other ranges/tnum to unbounded/unknown. */
|
||||
dst_reg->u32_min_value = 0;
|
||||
dst_reg->u32_max_value = U32_MAX;
|
||||
reset_reg64_and_tnum(dst_reg);
|
||||
}
|
||||
|
||||
static void scalar_min_max_sdiv(struct bpf_reg_state *dst_reg,
|
||||
struct bpf_reg_state *src_reg)
|
||||
{
|
||||
s64 *dst_smin = &dst_reg->smin_value;
|
||||
s64 *dst_smax = &dst_reg->smax_value;
|
||||
s64 src_val = src_reg->smin_value; /* non-zero, const divisor */
|
||||
s64 res1, res2;
|
||||
|
||||
/* BPF div specification: S64_MIN / -1 = S64_MIN */
|
||||
if (*dst_smin == S64_MIN && src_val == -1) {
|
||||
/*
|
||||
* If the dividend range contains more than just S64_MIN,
|
||||
* we cannot precisely track the result, so it becomes unbounded.
|
||||
* e.g., [S64_MIN, S64_MIN+10]/(-1),
|
||||
* = {S64_MIN} U [-(S64_MIN+10), -(S64_MIN+1)]
|
||||
* = {S64_MIN} U [S64_MAX-9, S64_MAX] = [S64_MIN, S64_MAX]
|
||||
* Otherwise (if dividend is exactly S64_MIN), result remains S64_MIN.
|
||||
*/
|
||||
if (*dst_smax != S64_MIN) {
|
||||
*dst_smin = S64_MIN;
|
||||
*dst_smax = S64_MAX;
|
||||
}
|
||||
goto reset;
|
||||
}
|
||||
|
||||
res1 = div64_s64(*dst_smin, src_val);
|
||||
res2 = div64_s64(*dst_smax, src_val);
|
||||
*dst_smin = min(res1, res2);
|
||||
*dst_smax = max(res1, res2);
|
||||
|
||||
reset:
|
||||
/* Reset other ranges/tnum to unbounded/unknown. */
|
||||
dst_reg->umin_value = 0;
|
||||
dst_reg->umax_value = U64_MAX;
|
||||
reset_reg32_and_tnum(dst_reg);
|
||||
}
|
||||
|
||||
static void scalar32_min_max_umod(struct bpf_reg_state *dst_reg,
|
||||
struct bpf_reg_state *src_reg)
|
||||
{
|
||||
u32 *dst_umin = &dst_reg->u32_min_value;
|
||||
u32 *dst_umax = &dst_reg->u32_max_value;
|
||||
u32 src_val = src_reg->u32_min_value; /* non-zero, const divisor */
|
||||
u32 res_max = src_val - 1;
|
||||
|
||||
/*
|
||||
* If dst_umax <= res_max, the result remains unchanged.
|
||||
* e.g., [2, 5] % 10 = [2, 5].
|
||||
*/
|
||||
if (*dst_umax <= res_max)
|
||||
return;
|
||||
|
||||
*dst_umin = 0;
|
||||
*dst_umax = min(*dst_umax, res_max);
|
||||
|
||||
/* Reset other ranges/tnum to unbounded/unknown. */
|
||||
dst_reg->s32_min_value = S32_MIN;
|
||||
dst_reg->s32_max_value = S32_MAX;
|
||||
reset_reg64_and_tnum(dst_reg);
|
||||
}
|
||||
|
||||
static void scalar_min_max_umod(struct bpf_reg_state *dst_reg,
|
||||
struct bpf_reg_state *src_reg)
|
||||
{
|
||||
u64 *dst_umin = &dst_reg->umin_value;
|
||||
u64 *dst_umax = &dst_reg->umax_value;
|
||||
u64 src_val = src_reg->umin_value; /* non-zero, const divisor */
|
||||
u64 res_max = src_val - 1;
|
||||
|
||||
/*
|
||||
* If dst_umax <= res_max, the result remains unchanged.
|
||||
* e.g., [2, 5] % 10 = [2, 5].
|
||||
*/
|
||||
if (*dst_umax <= res_max)
|
||||
return;
|
||||
|
||||
*dst_umin = 0;
|
||||
*dst_umax = min(*dst_umax, res_max);
|
||||
|
||||
/* Reset other ranges/tnum to unbounded/unknown. */
|
||||
dst_reg->smin_value = S64_MIN;
|
||||
dst_reg->smax_value = S64_MAX;
|
||||
reset_reg32_and_tnum(dst_reg);
|
||||
}
|
||||
|
||||
static void scalar32_min_max_smod(struct bpf_reg_state *dst_reg,
|
||||
struct bpf_reg_state *src_reg)
|
||||
{
|
||||
s32 *dst_smin = &dst_reg->s32_min_value;
|
||||
s32 *dst_smax = &dst_reg->s32_max_value;
|
||||
s32 src_val = src_reg->s32_min_value; /* non-zero, const divisor */
|
||||
|
||||
/*
|
||||
* Safe absolute value calculation:
|
||||
* If src_val == S32_MIN (-2147483648), src_abs becomes 2147483648.
|
||||
* Here use unsigned integer to avoid overflow.
|
||||
*/
|
||||
u32 src_abs = (src_val > 0) ? (u32)src_val : -(u32)src_val;
|
||||
|
||||
/*
|
||||
* Calculate the maximum possible absolute value of the result.
|
||||
* Even if src_abs is 2147483648 (S32_MIN), subtracting 1 gives
|
||||
* 2147483647 (S32_MAX), which fits perfectly in s32.
|
||||
*/
|
||||
s32 res_max_abs = src_abs - 1;
|
||||
|
||||
/*
|
||||
* If the dividend is already within the result range,
|
||||
* the result remains unchanged. e.g., [-2, 5] % 10 = [-2, 5].
|
||||
*/
|
||||
if (*dst_smin >= -res_max_abs && *dst_smax <= res_max_abs)
|
||||
return;
|
||||
|
||||
/* General case: result has the same sign as the dividend. */
|
||||
if (*dst_smin >= 0) {
|
||||
*dst_smin = 0;
|
||||
*dst_smax = min(*dst_smax, res_max_abs);
|
||||
} else if (*dst_smax <= 0) {
|
||||
*dst_smax = 0;
|
||||
*dst_smin = max(*dst_smin, -res_max_abs);
|
||||
} else {
|
||||
*dst_smin = -res_max_abs;
|
||||
*dst_smax = res_max_abs;
|
||||
}
|
||||
|
||||
/* Reset other ranges/tnum to unbounded/unknown. */
|
||||
dst_reg->u32_min_value = 0;
|
||||
dst_reg->u32_max_value = U32_MAX;
|
||||
reset_reg64_and_tnum(dst_reg);
|
||||
}
|
||||
|
||||
static void scalar_min_max_smod(struct bpf_reg_state *dst_reg,
|
||||
struct bpf_reg_state *src_reg)
|
||||
{
|
||||
s64 *dst_smin = &dst_reg->smin_value;
|
||||
s64 *dst_smax = &dst_reg->smax_value;
|
||||
s64 src_val = src_reg->smin_value; /* non-zero, const divisor */
|
||||
|
||||
/*
|
||||
* Safe absolute value calculation:
|
||||
* If src_val == S64_MIN (-2^63), src_abs becomes 2^63.
|
||||
* Here use unsigned integer to avoid overflow.
|
||||
*/
|
||||
u64 src_abs = (src_val > 0) ? (u64)src_val : -(u64)src_val;
|
||||
|
||||
/*
|
||||
* Calculate the maximum possible absolute value of the result.
|
||||
* Even if src_abs is 2^63 (S64_MIN), subtracting 1 gives
|
||||
* 2^63 - 1 (S64_MAX), which fits perfectly in s64.
|
||||
*/
|
||||
s64 res_max_abs = src_abs - 1;
|
||||
|
||||
/*
|
||||
* If the dividend is already within the result range,
|
||||
* the result remains unchanged. e.g., [-2, 5] % 10 = [-2, 5].
|
||||
*/
|
||||
if (*dst_smin >= -res_max_abs && *dst_smax <= res_max_abs)
|
||||
return;
|
||||
|
||||
/* General case: result has the same sign as the dividend. */
|
||||
if (*dst_smin >= 0) {
|
||||
*dst_smin = 0;
|
||||
*dst_smax = min(*dst_smax, res_max_abs);
|
||||
} else if (*dst_smax <= 0) {
|
||||
*dst_smax = 0;
|
||||
*dst_smin = max(*dst_smin, -res_max_abs);
|
||||
} else {
|
||||
*dst_smin = -res_max_abs;
|
||||
*dst_smax = res_max_abs;
|
||||
}
|
||||
|
||||
/* Reset other ranges/tnum to unbounded/unknown. */
|
||||
dst_reg->umin_value = 0;
|
||||
dst_reg->umax_value = U64_MAX;
|
||||
reset_reg32_and_tnum(dst_reg);
|
||||
}
|
||||
|
||||
static void scalar32_min_max_and(struct bpf_reg_state *dst_reg,
|
||||
struct bpf_reg_state *src_reg)
|
||||
{
|
||||
|
|
@ -15564,6 +15822,14 @@ static bool is_safe_to_compute_dst_reg_range(struct bpf_insn *insn,
|
|||
case BPF_MUL:
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Division and modulo operators range is only safe to compute when the
|
||||
* divisor is a constant.
|
||||
*/
|
||||
case BPF_DIV:
|
||||
case BPF_MOD:
|
||||
return src_is_const;
|
||||
|
||||
/* Shift operators range is only computable if shift dimension operand
|
||||
* is a constant. Shifts greater than 31 or 63 are undefined. This
|
||||
* includes shifts by a negative number.
|
||||
|
|
@ -15616,6 +15882,7 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
|
|||
struct bpf_reg_state src_reg)
|
||||
{
|
||||
u8 opcode = BPF_OP(insn->code);
|
||||
s16 off = insn->off;
|
||||
bool alu32 = (BPF_CLASS(insn->code) != BPF_ALU64);
|
||||
int ret;
|
||||
|
||||
|
|
@ -15667,6 +15934,38 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
|
|||
scalar32_min_max_mul(dst_reg, &src_reg);
|
||||
scalar_min_max_mul(dst_reg, &src_reg);
|
||||
break;
|
||||
case BPF_DIV:
|
||||
/* BPF div specification: x / 0 = 0 */
|
||||
if ((alu32 && src_reg.u32_min_value == 0) || (!alu32 && src_reg.umin_value == 0)) {
|
||||
___mark_reg_known(dst_reg, 0);
|
||||
break;
|
||||
}
|
||||
if (alu32)
|
||||
if (off == 1)
|
||||
scalar32_min_max_sdiv(dst_reg, &src_reg);
|
||||
else
|
||||
scalar32_min_max_udiv(dst_reg, &src_reg);
|
||||
else
|
||||
if (off == 1)
|
||||
scalar_min_max_sdiv(dst_reg, &src_reg);
|
||||
else
|
||||
scalar_min_max_udiv(dst_reg, &src_reg);
|
||||
break;
|
||||
case BPF_MOD:
|
||||
/* BPF mod specification: x % 0 = x */
|
||||
if ((alu32 && src_reg.u32_min_value == 0) || (!alu32 && src_reg.umin_value == 0))
|
||||
break;
|
||||
if (alu32)
|
||||
if (off == 1)
|
||||
scalar32_min_max_smod(dst_reg, &src_reg);
|
||||
else
|
||||
scalar32_min_max_umod(dst_reg, &src_reg);
|
||||
else
|
||||
if (off == 1)
|
||||
scalar_min_max_smod(dst_reg, &src_reg);
|
||||
else
|
||||
scalar_min_max_umod(dst_reg, &src_reg);
|
||||
break;
|
||||
case BPF_AND:
|
||||
if (tnum_is_const(src_reg.var_off)) {
|
||||
ret = maybe_fork_scalars(env, insn, dst_reg);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
#include "verifier_direct_packet_access.skel.h"
|
||||
#include "verifier_direct_stack_access_wraparound.skel.h"
|
||||
#include "verifier_div0.skel.h"
|
||||
#include "verifier_div_mod_bounds.skel.h"
|
||||
#include "verifier_div_overflow.skel.h"
|
||||
#include "verifier_global_subprogs.skel.h"
|
||||
#include "verifier_global_ptr_args.skel.h"
|
||||
|
|
@ -175,6 +176,7 @@ void test_verifier_d_path(void) { RUN(verifier_d_path); }
|
|||
void test_verifier_direct_packet_access(void) { RUN(verifier_direct_packet_access); }
|
||||
void test_verifier_direct_stack_access_wraparound(void) { RUN(verifier_direct_stack_access_wraparound); }
|
||||
void test_verifier_div0(void) { RUN(verifier_div0); }
|
||||
void test_verifier_div_mod_bounds(void) { RUN(verifier_div_mod_bounds); }
|
||||
void test_verifier_div_overflow(void) { RUN(verifier_div_overflow); }
|
||||
void test_verifier_global_subprogs(void) { RUN(verifier_global_subprogs); }
|
||||
void test_verifier_global_ptr_args(void) { RUN(verifier_global_ptr_args); }
|
||||
|
|
|
|||
1149
tools/testing/selftests/bpf/progs/verifier_div_mod_bounds.c
Normal file
1149
tools/testing/selftests/bpf/progs/verifier_div_mod_bounds.c
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -173,14 +173,15 @@ __naked void flow_keys_illegal_variable_offset_alu(void)
|
|||
asm volatile(" \
|
||||
r6 = r1; \
|
||||
r7 = *(u64*)(r6 + %[flow_keys_off]); \
|
||||
r8 = 8; \
|
||||
r8 /= 1; \
|
||||
call %[bpf_get_prandom_u32]; \
|
||||
r8 = r0; \
|
||||
r8 &= 8; \
|
||||
r7 += r8; \
|
||||
r0 = *(u64*)(r7 + 0); \
|
||||
exit; \
|
||||
" :
|
||||
: __imm_const(flow_keys_off, offsetof(struct __sk_buff, flow_keys))
|
||||
: __imm_const(flow_keys_off, offsetof(struct __sk_buff, flow_keys)),
|
||||
__imm(bpf_get_prandom_u32)
|
||||
: __clobber_all);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -229,11 +229,11 @@
|
|||
{
|
||||
"precise: program doesn't prematurely prune branches",
|
||||
.insns = {
|
||||
BPF_ALU64_IMM(BPF_MOV, BPF_REG_6, 0x400),
|
||||
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_get_prandom_u32),
|
||||
BPF_ALU64_REG(BPF_MOV, BPF_REG_6, BPF_REG_0),
|
||||
BPF_ALU64_IMM(BPF_MOV, BPF_REG_7, 0),
|
||||
BPF_ALU64_IMM(BPF_MOV, BPF_REG_8, 0),
|
||||
BPF_ALU64_IMM(BPF_MOV, BPF_REG_9, 0x80000000),
|
||||
BPF_ALU64_IMM(BPF_MOD, BPF_REG_6, 0x401),
|
||||
BPF_JMP_IMM(BPF_JA, 0, 0, 0),
|
||||
BPF_JMP_REG(BPF_JLE, BPF_REG_6, BPF_REG_9, 2),
|
||||
BPF_ALU64_IMM(BPF_MOD, BPF_REG_6, 1),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user