Skip to content

Commit 06c1c04

Browse files
gianlucaborellodavem330
authored andcommitted
bpf: allow helpers access to variable memory
Currently, helpers that read and write from/to the stack can do so using a pair of arguments of type ARG_PTR_TO_STACK and ARG_CONST_STACK_SIZE. ARG_CONST_STACK_SIZE accepts a constant register of type CONST_IMM, so that the verifier can safely check the memory access. However, requiring the argument to be a constant can be limiting in some circumstances. Since the current logic keeps track of the minimum and maximum value of a register throughout the simulated execution, ARG_CONST_STACK_SIZE can be changed to also accept an UNKNOWN_VALUE register in case its boundaries have been set and the range doesn't cause invalid memory accesses. One common situation when this is useful: int len; char buf[BUFSIZE]; /* BUFSIZE is 128 */ if (some_condition) len = 42; else len = 84; some_helper(..., buf, len & (BUFSIZE - 1)); The compiler can often decide to assign the constant values 42 or 48 into a variable on the stack, instead of keeping it in a register. When the variable is then read back from stack into the register in order to be passed to the helper, the verifier will not be able to recognize the register as constant (the verifier is not currently tracking all constant writes into memory), and the program won't be valid. However, by allowing the helper to accept an UNKNOWN_VALUE register, this program will work because the bitwise AND operation will set the range of possible values for the UNKNOWN_VALUE register to [0, BUFSIZE), so the verifier can guarantee the helper call will be safe (assuming the argument is of type ARG_CONST_STACK_SIZE_OR_ZERO, otherwise one more check against 0 would be needed). Custom ranges can be set not only with ALU operations, but also by explicitly comparing the UNKNOWN_VALUE register with constants. Another very common example happens when intercepting system call arguments and accessing user-provided data of variable size using bpf_probe_read(). One can load at runtime the user-provided length in an UNKNOWN_VALUE register, and then read that exact amount of data up to a compile-time determined limit in order to fit into the proper local storage allocated on the stack, without having to guess a suboptimal access size at compile time. Also, in case the helpers accepting the UNKNOWN_VALUE register operate in raw mode, disable the raw mode so that the program is required to initialize all memory, since there is no guarantee the helper will fill it completely, leaving possibilities for data leak (just relevant when the memory used by the helper is the stack, not when using a pointer to map element value or packet). In other words, ARG_PTR_TO_RAW_STACK will be treated as ARG_PTR_TO_STACK. Signed-off-by: Gianluca Borello <[email protected]> Acked-by: Daniel Borkmann <[email protected]> Signed-off-by: Alexei Starovoitov <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent f0318d0 commit 06c1c04

File tree

2 files changed

+474
-10
lines changed

2 files changed

+474
-10
lines changed

kernel/bpf/verifier.c

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,25 @@ static int check_stack_boundary(struct bpf_verifier_env *env, int regno,
980980
return 0;
981981
}
982982

983+
static int check_helper_mem_access(struct bpf_verifier_env *env, int regno,
984+
int access_size, bool zero_size_allowed,
985+
struct bpf_call_arg_meta *meta)
986+
{
987+
struct bpf_reg_state *regs = env->cur_state.regs;
988+
989+
switch (regs[regno].type) {
990+
case PTR_TO_PACKET:
991+
return check_packet_access(env, regno, 0, access_size);
992+
case PTR_TO_MAP_VALUE:
993+
return check_map_access(env, regno, 0, access_size);
994+
case PTR_TO_MAP_VALUE_ADJ:
995+
return check_map_access_adj(env, regno, 0, access_size);
996+
default: /* const_imm|ptr_to_stack or invalid ptr */
997+
return check_stack_boundary(env, regno, access_size,
998+
zero_size_allowed, meta);
999+
}
1000+
}
1001+
9831002
static int check_func_arg(struct bpf_verifier_env *env, u32 regno,
9841003
enum bpf_arg_type arg_type,
9851004
struct bpf_call_arg_meta *meta)
@@ -1018,7 +1037,10 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 regno,
10181037
} else if (arg_type == ARG_CONST_STACK_SIZE ||
10191038
arg_type == ARG_CONST_STACK_SIZE_OR_ZERO) {
10201039
expected_type = CONST_IMM;
1021-
if (type != expected_type)
1040+
/* One exception. Allow UNKNOWN_VALUE registers when the
1041+
* boundaries are known and don't cause unsafe memory accesses
1042+
*/
1043+
if (type != UNKNOWN_VALUE && type != expected_type)
10221044
goto err_type;
10231045
} else if (arg_type == ARG_CONST_MAP_PTR) {
10241046
expected_type = CONST_PTR_TO_MAP;
@@ -1099,15 +1121,47 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 regno,
10991121
verbose("ARG_CONST_STACK_SIZE cannot be first argument\n");
11001122
return -EACCES;
11011123
}
1102-
if (regs[regno - 1].type == PTR_TO_PACKET)
1103-
err = check_packet_access(env, regno - 1, 0, reg->imm);
1104-
else if (regs[regno - 1].type == PTR_TO_MAP_VALUE)
1105-
err = check_map_access(env, regno - 1, 0, reg->imm);
1106-
else if (regs[regno - 1].type == PTR_TO_MAP_VALUE_ADJ)
1107-
err = check_map_access_adj(env, regno - 1, 0, reg->imm);
1108-
else
1109-
err = check_stack_boundary(env, regno - 1, reg->imm,
1110-
zero_size_allowed, meta);
1124+
1125+
/* If the register is UNKNOWN_VALUE, the access check happens
1126+
* using its boundaries. Otherwise, just use its imm
1127+
*/
1128+
if (type == UNKNOWN_VALUE) {
1129+
/* For unprivileged variable accesses, disable raw
1130+
* mode so that the program is required to
1131+
* initialize all the memory that the helper could
1132+
* just partially fill up.
1133+
*/
1134+
meta = NULL;
1135+
1136+
if (reg->min_value < 0) {
1137+
verbose("R%d min value is negative, either use unsigned or 'var &= const'\n",
1138+
regno);
1139+
return -EACCES;
1140+
}
1141+
1142+
if (reg->min_value == 0) {
1143+
err = check_helper_mem_access(env, regno - 1, 0,
1144+
zero_size_allowed,
1145+
meta);
1146+
if (err)
1147+
return err;
1148+
}
1149+
1150+
if (reg->max_value == BPF_REGISTER_MAX_RANGE) {
1151+
verbose("R%d unbounded memory access, use 'var &= const' or 'if (var < const)'\n",
1152+
regno);
1153+
return -EACCES;
1154+
}
1155+
err = check_helper_mem_access(env, regno - 1,
1156+
reg->max_value,
1157+
zero_size_allowed, meta);
1158+
if (err)
1159+
return err;
1160+
} else {
1161+
/* register is CONST_IMM */
1162+
err = check_helper_mem_access(env, regno - 1, reg->imm,
1163+
zero_size_allowed, meta);
1164+
}
11111165
}
11121166

11131167
return err;

0 commit comments

Comments
 (0)