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

bpf: Fix overloading of MEM_UNINIT's meaning

Lonial reported an issue in the BPF verifier where check_mem_size_reg()
has the following code:

if (!tnum_is_const(reg->var_off))
/* For unprivileged variable accesses, disable raw
* mode so that the program is required to
* initialize all the memory that the helper could
* just partially fill up.
*/
meta = NULL;

This means that writes are not checked when the register containing the
size of the passed buffer has not a fixed size. Through this bug, a BPF
program can write to a map which is marked as read-only, for example,
.rodata global maps.

The problem is that MEM_UNINIT's initial meaning that "the passed buffer
to the BPF helper does not need to be initialized" which was added back
in commit 435faee1aae9 ("bpf, verifier: add ARG_PTR_TO_RAW_STACK type")
got overloaded over time with "the passed buffer is being written to".

The problem however is that checks such as the above which were added later
via 06c1c049721a ("bpf: allow helpers access to variable memory") set meta
to NULL in order force the user to always initialize the passed buffer to
the helper. Due to the current double meaning of MEM_UNINIT, this bypasses
verifier write checks to the memory (not boundary checks though) and only
assumes the latter memory is read instead.

Fix this by reverting MEM_UNINIT back to its original meaning, and having
MEM_WRITE as an annotation to BPF helpers in order to then trigger the
BPF verifier checks for writing to memory.

Some notes: check_arg_pair_ok() ensures that for ARG_CONST_SIZE{,_OR_ZERO}
we can access fn->arg_type[arg - 1] since it must contain a preceding
ARG_PTR_TO_MEM. For check_mem_reg() the meta argument can be removed
altogether since we do check both BPF_READ and BPF_WRITE. Same for the
equivalent check_kfunc_mem_size_reg().

Fixes: 7b3552d3f9f6 ("bpf: Reject writes for PTR_TO_MAP_KEY in check_helper_mem_access")
Fixes: 97e6d7dab1ca ("bpf: Check PTR_TO_MEM | MEM_RDONLY in check_helper_mem_access")
Fixes: 15baa55ff5b0 ("bpf/verifier: allow all functions to read user provided context")
Reported-by: Lonial Con <kongln9170@gmail.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20241021152809.33343-2-daniel@iogearbox.net
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Daniel Borkmann and committed by
Alexei Starovoitov
8ea60733 6fad274f

+35 -38
+35 -38
kernel/bpf/verifier.c
··· 7438 7438 } 7439 7439 7440 7440 static int check_helper_mem_access(struct bpf_verifier_env *env, int regno, 7441 - int access_size, bool zero_size_allowed, 7441 + int access_size, enum bpf_access_type access_type, 7442 + bool zero_size_allowed, 7442 7443 struct bpf_call_arg_meta *meta) 7443 7444 { 7444 7445 struct bpf_reg_state *regs = cur_regs(env), *reg = &regs[regno]; ··· 7451 7450 return check_packet_access(env, regno, reg->off, access_size, 7452 7451 zero_size_allowed); 7453 7452 case PTR_TO_MAP_KEY: 7454 - if (meta && meta->raw_mode) { 7453 + if (access_type == BPF_WRITE) { 7455 7454 verbose(env, "R%d cannot write into %s\n", regno, 7456 7455 reg_type_str(env, reg->type)); 7457 7456 return -EACCES; ··· 7459 7458 return check_mem_region_access(env, regno, reg->off, access_size, 7460 7459 reg->map_ptr->key_size, false); 7461 7460 case PTR_TO_MAP_VALUE: 7462 - if (check_map_access_type(env, regno, reg->off, access_size, 7463 - meta && meta->raw_mode ? BPF_WRITE : 7464 - BPF_READ)) 7461 + if (check_map_access_type(env, regno, reg->off, access_size, access_type)) 7465 7462 return -EACCES; 7466 7463 return check_map_access(env, regno, reg->off, access_size, 7467 7464 zero_size_allowed, ACCESS_HELPER); 7468 7465 case PTR_TO_MEM: 7469 7466 if (type_is_rdonly_mem(reg->type)) { 7470 - if (meta && meta->raw_mode) { 7467 + if (access_type == BPF_WRITE) { 7471 7468 verbose(env, "R%d cannot write into %s\n", regno, 7472 7469 reg_type_str(env, reg->type)); 7473 7470 return -EACCES; ··· 7476 7477 zero_size_allowed); 7477 7478 case PTR_TO_BUF: 7478 7479 if (type_is_rdonly_mem(reg->type)) { 7479 - if (meta && meta->raw_mode) { 7480 + if (access_type == BPF_WRITE) { 7480 7481 verbose(env, "R%d cannot write into %s\n", regno, 7481 7482 reg_type_str(env, reg->type)); 7482 7483 return -EACCES; ··· 7504 7505 * Dynamically check it now. 7505 7506 */ 7506 7507 if (!env->ops->convert_ctx_access) { 7507 - enum bpf_access_type atype = meta && meta->raw_mode ? BPF_WRITE : BPF_READ; 7508 7508 int offset = access_size - 1; 7509 7509 7510 7510 /* Allow zero-byte read from PTR_TO_CTX */ ··· 7511 7513 return zero_size_allowed ? 0 : -EACCES; 7512 7514 7513 7515 return check_mem_access(env, env->insn_idx, regno, offset, BPF_B, 7514 - atype, -1, false, false); 7516 + access_type, -1, false, false); 7515 7517 } 7516 7518 7517 7519 fallthrough; ··· 7536 7538 */ 7537 7539 static int check_mem_size_reg(struct bpf_verifier_env *env, 7538 7540 struct bpf_reg_state *reg, u32 regno, 7541 + enum bpf_access_type access_type, 7539 7542 bool zero_size_allowed, 7540 7543 struct bpf_call_arg_meta *meta) 7541 7544 { ··· 7552 7553 */ 7553 7554 meta->msize_max_value = reg->umax_value; 7554 7555 7555 - /* The register is SCALAR_VALUE; the access check 7556 - * happens using its boundaries. 7556 + /* The register is SCALAR_VALUE; the access check happens using 7557 + * its boundaries. For unprivileged variable accesses, disable 7558 + * raw mode so that the program is required to initialize all 7559 + * the memory that the helper could just partially fill up. 7557 7560 */ 7558 7561 if (!tnum_is_const(reg->var_off)) 7559 - /* For unprivileged variable accesses, disable raw 7560 - * mode so that the program is required to 7561 - * initialize all the memory that the helper could 7562 - * just partially fill up. 7563 - */ 7564 7562 meta = NULL; 7565 7563 7566 7564 if (reg->smin_value < 0) { ··· 7577 7581 regno); 7578 7582 return -EACCES; 7579 7583 } 7580 - err = check_helper_mem_access(env, regno - 1, 7581 - reg->umax_value, 7582 - zero_size_allowed, meta); 7584 + err = check_helper_mem_access(env, regno - 1, reg->umax_value, 7585 + access_type, zero_size_allowed, meta); 7583 7586 if (!err) 7584 7587 err = mark_chain_precision(env, regno); 7585 7588 return err; ··· 7589 7594 { 7590 7595 bool may_be_null = type_may_be_null(reg->type); 7591 7596 struct bpf_reg_state saved_reg; 7592 - struct bpf_call_arg_meta meta; 7593 7597 int err; 7594 7598 7595 7599 if (register_is_null(reg)) 7596 7600 return 0; 7597 7601 7598 - memset(&meta, 0, sizeof(meta)); 7599 7602 /* Assuming that the register contains a value check if the memory 7600 7603 * access is safe. Temporarily save and restore the register's state as 7601 7604 * the conversion shouldn't be visible to a caller. ··· 7603 7610 mark_ptr_not_null_reg(reg); 7604 7611 } 7605 7612 7606 - err = check_helper_mem_access(env, regno, mem_size, true, &meta); 7607 - /* Check access for BPF_WRITE */ 7608 - meta.raw_mode = true; 7609 - err = err ?: check_helper_mem_access(env, regno, mem_size, true, &meta); 7613 + err = check_helper_mem_access(env, regno, mem_size, BPF_READ, true, NULL); 7614 + err = err ?: check_helper_mem_access(env, regno, mem_size, BPF_WRITE, true, NULL); 7610 7615 7611 7616 if (may_be_null) 7612 7617 *reg = saved_reg; ··· 7630 7639 mark_ptr_not_null_reg(mem_reg); 7631 7640 } 7632 7641 7633 - err = check_mem_size_reg(env, reg, regno, true, &meta); 7634 - /* Check access for BPF_WRITE */ 7635 - meta.raw_mode = true; 7636 - err = err ?: check_mem_size_reg(env, reg, regno, true, &meta); 7642 + err = check_mem_size_reg(env, reg, regno, BPF_READ, true, &meta); 7643 + err = err ?: check_mem_size_reg(env, reg, regno, BPF_WRITE, true, &meta); 7637 7644 7638 7645 if (may_be_null) 7639 7646 *mem_reg = saved_reg; 7647 + 7640 7648 return err; 7641 7649 } 7642 7650 ··· 8938 8948 verbose(env, "invalid map_ptr to access map->key\n"); 8939 8949 return -EACCES; 8940 8950 } 8941 - err = check_helper_mem_access(env, regno, 8942 - meta->map_ptr->key_size, false, 8943 - NULL); 8951 + err = check_helper_mem_access(env, regno, meta->map_ptr->key_size, 8952 + BPF_READ, false, NULL); 8944 8953 break; 8945 8954 case ARG_PTR_TO_MAP_VALUE: 8946 8955 if (type_may_be_null(arg_type) && register_is_null(reg)) ··· 8954 8965 return -EACCES; 8955 8966 } 8956 8967 meta->raw_mode = arg_type & MEM_UNINIT; 8957 - err = check_helper_mem_access(env, regno, 8958 - meta->map_ptr->value_size, false, 8959 - meta); 8968 + err = check_helper_mem_access(env, regno, meta->map_ptr->value_size, 8969 + arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ, 8970 + false, meta); 8960 8971 break; 8961 8972 case ARG_PTR_TO_PERCPU_BTF_ID: 8962 8973 if (!reg->btf_id) { ··· 8998 9009 */ 8999 9010 meta->raw_mode = arg_type & MEM_UNINIT; 9000 9011 if (arg_type & MEM_FIXED_SIZE) { 9001 - err = check_helper_mem_access(env, regno, fn->arg_size[arg], false, meta); 9012 + err = check_helper_mem_access(env, regno, fn->arg_size[arg], 9013 + arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ, 9014 + false, meta); 9002 9015 if (err) 9003 9016 return err; 9004 9017 if (arg_type & MEM_ALIGNED) ··· 9008 9017 } 9009 9018 break; 9010 9019 case ARG_CONST_SIZE: 9011 - err = check_mem_size_reg(env, reg, regno, false, meta); 9020 + err = check_mem_size_reg(env, reg, regno, 9021 + fn->arg_type[arg - 1] & MEM_WRITE ? 9022 + BPF_WRITE : BPF_READ, 9023 + false, meta); 9012 9024 break; 9013 9025 case ARG_CONST_SIZE_OR_ZERO: 9014 - err = check_mem_size_reg(env, reg, regno, true, meta); 9026 + err = check_mem_size_reg(env, reg, regno, 9027 + fn->arg_type[arg - 1] & MEM_WRITE ? 9028 + BPF_WRITE : BPF_READ, 9029 + true, meta); 9015 9030 break; 9016 9031 case ARG_PTR_TO_DYNPTR: 9017 9032 err = process_dynptr_func(env, regno, insn_idx, arg_type, 0);