mirror of
https://github.com/Fishwaldo/linux-bl808.git
synced 2025-03-30 19:07:15 +00:00
bpf: fix integer overflows
There were various issues related to the limited size of integers used in
the verifier:
- `off + size` overflow in __check_map_access()
- `off + reg->off` overflow in check_mem_access()
- `off + reg->var_off.value` overflow or 32-bit truncation of
`reg->var_off.value` in check_mem_access()
- 32-bit truncation in check_stack_boundary()
Make sure that any integer math cannot overflow by not allowing
pointer math with large values.
Also reduce the scope of "scalar op scalar" tracking.
Fixes: f1174f77b5
("bpf/verifier: rework value tracking")
Reported-by: Jann Horn <jannh@google.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
This commit is contained in:
parent
179d1c5602
commit
bb7f0f989c
2 changed files with 50 additions and 2 deletions
|
@ -15,11 +15,11 @@
|
||||||
* In practice this is far bigger than any realistic pointer offset; this limit
|
* In practice this is far bigger than any realistic pointer offset; this limit
|
||||||
* ensures that umax_value + (int)off + (int)size cannot overflow a u64.
|
* ensures that umax_value + (int)off + (int)size cannot overflow a u64.
|
||||||
*/
|
*/
|
||||||
#define BPF_MAX_VAR_OFF (1ULL << 31)
|
#define BPF_MAX_VAR_OFF (1 << 29)
|
||||||
/* Maximum variable size permitted for ARG_CONST_SIZE[_OR_ZERO]. This ensures
|
/* Maximum variable size permitted for ARG_CONST_SIZE[_OR_ZERO]. This ensures
|
||||||
* that converting umax_value to int cannot overflow.
|
* that converting umax_value to int cannot overflow.
|
||||||
*/
|
*/
|
||||||
#define BPF_MAX_VAR_SIZ INT_MAX
|
#define BPF_MAX_VAR_SIZ (1 << 29)
|
||||||
|
|
||||||
/* Liveness marks, used for registers and spilled-regs (in stack slots).
|
/* Liveness marks, used for registers and spilled-regs (in stack slots).
|
||||||
* Read marks propagate upwards until they find a write mark; they record that
|
* Read marks propagate upwards until they find a write mark; they record that
|
||||||
|
|
|
@ -1819,6 +1819,41 @@ static bool signed_sub_overflows(s64 a, s64 b)
|
||||||
return res > a;
|
return res > a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool check_reg_sane_offset(struct bpf_verifier_env *env,
|
||||||
|
const struct bpf_reg_state *reg,
|
||||||
|
enum bpf_reg_type type)
|
||||||
|
{
|
||||||
|
bool known = tnum_is_const(reg->var_off);
|
||||||
|
s64 val = reg->var_off.value;
|
||||||
|
s64 smin = reg->smin_value;
|
||||||
|
|
||||||
|
if (known && (val >= BPF_MAX_VAR_OFF || val <= -BPF_MAX_VAR_OFF)) {
|
||||||
|
verbose(env, "math between %s pointer and %lld is not allowed\n",
|
||||||
|
reg_type_str[type], val);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reg->off >= BPF_MAX_VAR_OFF || reg->off <= -BPF_MAX_VAR_OFF) {
|
||||||
|
verbose(env, "%s pointer offset %d is not allowed\n",
|
||||||
|
reg_type_str[type], reg->off);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (smin == S64_MIN) {
|
||||||
|
verbose(env, "math between %s pointer and register with unbounded min value is not allowed\n",
|
||||||
|
reg_type_str[type]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (smin >= BPF_MAX_VAR_OFF || smin <= -BPF_MAX_VAR_OFF) {
|
||||||
|
verbose(env, "value %lld makes %s pointer be out of bounds\n",
|
||||||
|
smin, reg_type_str[type]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/* Handles arithmetic on a pointer and a scalar: computes new min/max and var_off.
|
/* Handles arithmetic on a pointer and a scalar: computes new min/max and var_off.
|
||||||
* Caller should also handle BPF_MOV case separately.
|
* Caller should also handle BPF_MOV case separately.
|
||||||
* If we return -EACCES, caller may want to try again treating pointer as a
|
* If we return -EACCES, caller may want to try again treating pointer as a
|
||||||
|
@ -1887,6 +1922,10 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
|
||||||
dst_reg->type = ptr_reg->type;
|
dst_reg->type = ptr_reg->type;
|
||||||
dst_reg->id = ptr_reg->id;
|
dst_reg->id = ptr_reg->id;
|
||||||
|
|
||||||
|
if (!check_reg_sane_offset(env, off_reg, ptr_reg->type) ||
|
||||||
|
!check_reg_sane_offset(env, ptr_reg, ptr_reg->type))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case BPF_ADD:
|
case BPF_ADD:
|
||||||
/* We can take a fixed offset as long as it doesn't overflow
|
/* We can take a fixed offset as long as it doesn't overflow
|
||||||
|
@ -2017,6 +2056,9 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
|
||||||
return -EACCES;
|
return -EACCES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!check_reg_sane_offset(env, dst_reg, ptr_reg->type))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
__update_reg_bounds(dst_reg);
|
__update_reg_bounds(dst_reg);
|
||||||
__reg_deduce_bounds(dst_reg);
|
__reg_deduce_bounds(dst_reg);
|
||||||
__reg_bound_offset(dst_reg);
|
__reg_bound_offset(dst_reg);
|
||||||
|
@ -2046,6 +2088,12 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
|
||||||
src_known = tnum_is_const(src_reg.var_off);
|
src_known = tnum_is_const(src_reg.var_off);
|
||||||
dst_known = tnum_is_const(dst_reg->var_off);
|
dst_known = tnum_is_const(dst_reg->var_off);
|
||||||
|
|
||||||
|
if (!src_known &&
|
||||||
|
opcode != BPF_ADD && opcode != BPF_SUB && opcode != BPF_AND) {
|
||||||
|
__mark_reg_unknown(dst_reg);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case BPF_ADD:
|
case BPF_ADD:
|
||||||
if (signed_add_overflows(dst_reg->smin_value, smin_val) ||
|
if (signed_add_overflows(dst_reg->smin_value, smin_val) ||
|
||||||
|
|
Loading…
Add table
Reference in a new issue