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

bpf: Check attach_func_proto more carefully in check_return_code

Syzkaller reports the following crash:

RIP: 0010:check_return_code kernel/bpf/verifier.c:10575 [inline]
RIP: 0010:do_check kernel/bpf/verifier.c:12346 [inline]
RIP: 0010:do_check_common+0xb3d2/0xd250 kernel/bpf/verifier.c:14610

With the following reproducer:

bpf$PROG_LOAD_XDP(0x5, &(0x7f00000004c0)={0xd, 0x3, &(0x7f0000000000)=ANY=[@ANYBLOB="1800000000000019000000000000000095"], &(0x7f0000000300)='GPL\x00', 0x0, 0x0, 0x0, 0x0, 0x0, '\x00', 0x0, 0x2b, 0xffffffffffffffff, 0x8, 0x0, 0x0, 0x10, 0x0}, 0x80)

Because we don't enforce expected_attach_type for XDP programs,
we end up in hitting 'if (prog->expected_attach_type == BPF_LSM_CGROUP'
part in check_return_code and follow up with testing
`prog->aux->attach_func_proto->type`, but `prog->aux->attach_func_proto`
is NULL.

Add explicit prog_type check for the "Note, BPF_LSM_CGROUP that
attach ..." condition. Also, don't skip return code check for
LSM/STRUCT_OPS.

The above actually brings an issue with existing selftest which
tries to return EPERM from void inet_csk_clone. Fix the
test (and move called_socket_clone to make sure it's not
incremented in case of an error) and add a new one to explicitly
verify this condition.

Fixes: 69fd337a975c ("bpf: per-cgroup lsm flavor")
Reported-by: syzbot+5cc0730bd4b4d2c5f152@syzkaller.appspotmail.com
Signed-off-by: Stanislav Fomichev <sdf@google.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Martin KaFai Lau <kafai@fb.com>
Link: https://lore.kernel.org/bpf/20220708175000.2603078-1-sdf@google.com

authored by

Stanislav Fomichev and committed by
Daniel Borkmann
d1a6edec 32e0d9b3

+48 -11
+16 -5
kernel/bpf/verifier.c
··· 10444 10444 const bool is_subprog = frame->subprogno; 10445 10445 10446 10446 /* LSM and struct_ops func-ptr's return type could be "void" */ 10447 - if (!is_subprog && 10448 - (prog_type == BPF_PROG_TYPE_STRUCT_OPS || 10449 - prog_type == BPF_PROG_TYPE_LSM) && 10450 - !prog->aux->attach_func_proto->type) 10451 - return 0; 10447 + if (!is_subprog) { 10448 + switch (prog_type) { 10449 + case BPF_PROG_TYPE_LSM: 10450 + if (prog->expected_attach_type == BPF_LSM_CGROUP) 10451 + /* See below, can be 0 or 0-1 depending on hook. */ 10452 + break; 10453 + fallthrough; 10454 + case BPF_PROG_TYPE_STRUCT_OPS: 10455 + if (!prog->aux->attach_func_proto->type) 10456 + return 0; 10457 + break; 10458 + default: 10459 + break; 10460 + } 10461 + } 10452 10462 10453 10463 /* eBPF calling convention is such that R0 is used 10454 10464 * to return the value from eBPF program. ··· 10582 10572 if (!tnum_in(range, reg->var_off)) { 10583 10573 verbose_invalid_scalar(env, reg, &range, "program exit", "R0"); 10584 10574 if (prog->expected_attach_type == BPF_LSM_CGROUP && 10575 + prog_type == BPF_PROG_TYPE_LSM && 10585 10576 !prog->aux->attach_func_proto->type) 10586 10577 verbose(env, "Note, BPF_LSM_CGROUP that attach to void LSM hooks can't modify return value!\n"); 10587 10578 return -EINVAL;
+12
tools/testing/selftests/bpf/prog_tests/lsm_cgroup.c
··· 6 6 #include <bpf/btf.h> 7 7 8 8 #include "lsm_cgroup.skel.h" 9 + #include "lsm_cgroup_nonvoid.skel.h" 9 10 #include "cgroup_helpers.h" 10 11 #include "network_helpers.h" 11 12 ··· 294 293 lsm_cgroup__destroy(skel); 295 294 } 296 295 296 + static void test_lsm_cgroup_nonvoid(void) 297 + { 298 + struct lsm_cgroup_nonvoid *skel = NULL; 299 + 300 + skel = lsm_cgroup_nonvoid__open_and_load(); 301 + ASSERT_NULL(skel, "open succeeds"); 302 + lsm_cgroup_nonvoid__destroy(skel); 303 + } 304 + 297 305 void test_lsm_cgroup(void) 298 306 { 299 307 if (test__start_subtest("functional")) 300 308 test_lsm_cgroup_functional(); 309 + if (test__start_subtest("nonvoid")) 310 + test_lsm_cgroup_nonvoid(); 301 311 btf__free(btf); 302 312 }
+6 -6
tools/testing/selftests/bpf/progs/lsm_cgroup.c
··· 156 156 { 157 157 int prio = 234; 158 158 159 - called_socket_clone++; 160 - 161 159 if (!newsk) 162 160 return 1; 163 161 164 162 /* Accepted request sockets get a different priority. */ 165 163 if (bpf_setsockopt(newsk, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio))) 166 - return 0; /* EPERM */ 164 + return 1; 167 165 168 166 /* Make sure bpf_getsockopt is allowed and works. */ 169 167 prio = 0; 170 168 if (bpf_getsockopt(newsk, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio))) 171 - return 0; /* EPERM */ 169 + return 1; 172 170 if (prio != 234) 173 - return 0; /* EPERM */ 171 + return 1; 174 172 175 173 /* Can access cgroup local storage. */ 176 174 if (!test_local_storage()) 177 - return 0; /* EPERM */ 175 + return 1; 176 + 177 + called_socket_clone++; 178 178 179 179 return 1; 180 180 }
+14
tools/testing/selftests/bpf/progs/lsm_cgroup_nonvoid.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + #include "vmlinux.h" 4 + #include <bpf/bpf_helpers.h> 5 + #include <bpf/bpf_tracing.h> 6 + 7 + char _license[] SEC("license") = "GPL"; 8 + 9 + SEC("lsm_cgroup/inet_csk_clone") 10 + int BPF_PROG(nonvoid_socket_clone, struct sock *newsk, const struct request_sock *req) 11 + { 12 + /* Can not return any errors from void LSM hooks. */ 13 + return 0; 14 + }