Improves '7ae4eb14c5f9 ("bpf: Add ksock kfuncs")' verifier filter to avoid recursion from calling bpf_ksock_send() from the security_socket_sendmsg() LSM hooks. The filter was unnecessarily restrictive, this makes sure only bpf_ksock_send() is forbidden and not the bpf_ksock_acquire() and bpf_ksock_release() kfuncs that are part of the same set but are safe to be called in that context. Signed-off-by: Mahe Tardy --- net/core/bpf_ksock.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/net/core/bpf_ksock.c b/net/core/bpf_ksock.c index 48a2e2327ace..6c508c7a0adc 100644 --- a/net/core/bpf_ksock.c +++ b/net/core/bpf_ksock.c @@ -264,6 +264,7 @@ BTF_ID(func, bpf_lsm_socket_sendmsg) #else BTF_ID_UNUSED #endif +BTF_ID_LIST_SINGLE(bpf_ksock_send_id, func, bpf_ksock_send) #endif static int bpf_ksock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id) @@ -275,9 +276,13 @@ static int bpf_ksock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id) return 0; #ifdef CONFIG_BPF_LSM - if (prog->type == BPF_PROG_TYPE_LSM && - prog->aux->attach_btf_id != bpf_lsm_socket_sendmsg_id[0]) + if (prog->type == BPF_PROG_TYPE_LSM) { + if (prog->aux->attach_btf_id == bpf_lsm_socket_sendmsg_id[0] && + kfunc_id == bpf_ksock_send_id[0]) + return -EACCES; + return 0; + } #endif return -EACCES; -- 2.43.0 This test makes sure that bpf_ksock_acquire() and bpf_ksock_release() can be called from programs attached to ksock_socket_sendmsg(). Signed-off-by: Mahe Tardy --- .../selftests/bpf/progs/ksock_lsm_verifier.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c b/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c index fd2ccfdb5802..740ed370d28f 100644 --- a/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c +++ b/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c @@ -12,7 +12,7 @@ char send_data[11] = "dummy data"; SEC("lsm.s/socket_sendmsg") __description("bpf_ksock_send is rejected from socket_sendmsg LSM hook") __failure __msg("calling kernel function bpf_ksock_send is not allowed") -int BPF_PROG(ksock_socket_sendmsg, struct socket *sock, struct msghdr *msg, +int BPF_PROG(socket_sendmsg_ksock_send, struct socket *sock, struct msghdr *msg, int size, int ret) { struct __ksock_ctx_value *v; @@ -32,4 +32,21 @@ int BPF_PROG(ksock_socket_sendmsg, struct socket *sock, struct msghdr *msg, return ret; } +SEC("lsm.s/socket_sendmsg") +__description("bpf_ksock operations other than send are allowed from socket_sendmsg LSM hook") +__success +int BPF_PROG(socket_sendmsg_ksock_allowed, struct socket *sock, + struct msghdr *msg, int size, int ret) +{ + struct bpf_ksock *ks; + + ks = ksock_ctx_get(); + if (!ks) + return ret; + + bpf_ksock_release(ks); + return ret; +} + + char __license[] SEC("license") = "GPL"; -- 2.43.0 This allows to ksock_send() to various destinations by allowing to call ksock_connect() in the send path instead of initializing it once for all in the SYSCALL program. It was limiting in cases ksock was used to reply to specific destinations. We need to break this potential cycle between the two LSM hooks and BPF programs calling kfuncs triggering themselves: socket_sendmsg program bpf_ksock_connect() connect_socket() -> security_socket_connect() socket_connect program bpf_ksock_send() kernel_sendmsg() -> sock_sendmsg() -> __sock_sendmsg() security_socket_sendmsg() socket_sendmsg program Breaking this on one edge is sufficient, so in this commit we prevent bpf_ksock_connect() to be called from LSM programs attached to the security_socket_sendmsg() hook. Signed-off-by: Mahe Tardy --- net/core/bpf_ksock.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/net/core/bpf_ksock.c b/net/core/bpf_ksock.c index 6c508c7a0adc..87329c4a2f8e 100644 --- a/net/core/bpf_ksock.c +++ b/net/core/bpf_ksock.c @@ -144,7 +144,7 @@ bpf_ksock_create(const struct bpf_ksock_create_opts *opts, u32 opts__sz, * Connects the socket to the specified remote address and port. * * This function may sleep while connecting the socket, so it can only be used - * in sleepable BPF programs (SYSCALL). + * in sleepable BPF programs. * * Return: 0 on success, negative errno on error. */ @@ -243,7 +243,6 @@ __bpf_kfunc_end_defs(); BTF_KFUNCS_START(ksock_init_kfunc_btf_ids) BTF_ID_FLAGS(func, bpf_ksock_create, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_ksock_connect, KF_SLEEPABLE) BTF_KFUNCS_END(ksock_init_kfunc_btf_ids) static const struct btf_kfunc_id_set ksock_init_kfunc_set = { @@ -254,6 +253,7 @@ static const struct btf_kfunc_id_set ksock_init_kfunc_set = { BTF_KFUNCS_START(ksock_kfunc_btf_ids) BTF_ID_FLAGS(func, bpf_ksock_release, KF_RELEASE) BTF_ID_FLAGS(func, bpf_ksock_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NULL) +BTF_ID_FLAGS(func, bpf_ksock_connect, KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_ksock_send, KF_SLEEPABLE) BTF_KFUNCS_END(ksock_kfunc_btf_ids) @@ -265,6 +265,14 @@ BTF_ID(func, bpf_lsm_socket_sendmsg) BTF_ID_UNUSED #endif BTF_ID_LIST_SINGLE(bpf_ksock_send_id, func, bpf_ksock_send) + +BTF_ID_LIST(bpf_lsm_socket_connect_id) +#ifdef CONFIG_SECURITY_NETWORK +BTF_ID(func, bpf_lsm_socket_connect) +#else +BTF_ID_UNUSED +#endif +BTF_ID_LIST_SINGLE(bpf_ksock_connect_id, func, bpf_ksock_connect) #endif static int bpf_ksock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id) @@ -278,7 +286,17 @@ static int bpf_ksock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id) #ifdef CONFIG_BPF_LSM if (prog->type == BPF_PROG_TYPE_LSM) { if (prog->aux->attach_btf_id == bpf_lsm_socket_sendmsg_id[0] && - kfunc_id == bpf_ksock_send_id[0]) + (kfunc_id == bpf_ksock_send_id[0] || + /* + * Break the socket_sendmsg -> bpf_ksock_connect() -> + * socket_connect -> bpf_ksock_send() -> socket_sendmsg' + * cycle. Blocking this edge is enough. + */ + kfunc_id == bpf_ksock_connect_id[0])) + return -EACCES; + + if (prog->aux->attach_btf_id == bpf_lsm_socket_connect_id[0] && + kfunc_id == bpf_ksock_connect_id[0]) return -EACCES; return 0; -- 2.43.0 Tests that the verifier filter prevents bpf_ksock_connect() to be called in a LSM program attached to security_socket_connect() as it would create a direct recursion. Also checks that the other LSM hook unrelated kfuncs can be called. Moreover, checks that calling bpf_ksock_connect() is prevented also from security_socket_sendmsg() as it would allow to create two BPF programs that would call each other in a cycle through LSMs. Signed-off-by: Mahe Tardy --- .../selftests/bpf/progs/ksock_lsm_verifier.c | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c b/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c index 740ed370d28f..7b5d118e5750 100644 --- a/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c +++ b/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c @@ -48,5 +48,64 @@ int BPF_PROG(socket_sendmsg_ksock_allowed, struct socket *sock, return ret; } +SEC("lsm.s/socket_sendmsg") +__description("bpf_ksock_connect is rejected from socket_sendmsg LSM hook") +__failure __msg("calling kernel function bpf_ksock_connect is not allowed") +int BPF_PROG(socket_sendmsg_ksock_connect, struct socket *sock, + struct msghdr *msg, int size, int ret) +{ + struct bpf_ksock *ks; + union bpf_ksock_addr addr = {}; + + ks = ksock_ctx_get(); + if (!ks) + return ret; + + bpf_ksock_connect(ks, &addr, sizeof(addr)); + bpf_ksock_release(ks); + return ret; +} + +SEC("lsm.s/socket_connect") +__description("bpf_ksock_connect is rejected from socket_connect LSM hook") +__failure __msg("calling kernel function bpf_ksock_connect is not allowed") +int BPF_PROG(socket_connect_ksock_connect, struct socket *sock, struct sockaddr *address, + int addrlen, int ret) +{ + struct __ksock_ctx_value *v; + union bpf_ksock_addr addr = {}; + struct bpf_ksock *ks; + + v = ksock_ctx_value_lookup(); + if (!v) + return ret; + + ks = bpf_kptr_xchg(&v->ctx, NULL); + if (!ks) + return ret; + + bpf_ksock_connect(ks, &addr, sizeof(addr)); + bpf_ksock_release(ks); + + return ret; +} + +SEC("lsm.s/socket_connect") +__description("bpf_ksock operations other than connect are allowed from socket_connect LSM hook") +__success +int BPF_PROG(socket_connect_ksock_allowed, struct socket *sock, + struct sockaddr *address, int addrlen, int ret) +{ + struct bpf_ksock *ks; + + ks = ksock_ctx_get(); + if (!ks) + return ret; + + bpf_ksock_send(ks, send_data, sizeof(send_data)); + bpf_ksock_release(ks); + + return ret; +} char __license[] SEC("license") = "GPL"; -- 2.43.0 Tests more consistently that calling bpf_ksock_send() from unconnected ksock socket fails and that bpf_ksock_connect() can be called from the SYSCALL program creating the socket or from the LSM program also calling bpf_ksock_send(). Signed-off-by: Mahe Tardy --- .../testing/selftests/bpf/prog_tests/ksock.c | 53 +++++++++++++++---- tools/testing/selftests/bpf/progs/ksock_lsm.c | 32 ++++++++--- 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/ksock.c b/tools/testing/selftests/bpf/prog_tests/ksock.c index dd6b167623d9..78677bfffdf3 100644 --- a/tools/testing/selftests/bpf/prog_tests/ksock.c +++ b/tools/testing/selftests/bpf/prog_tests/ksock.c @@ -17,6 +17,12 @@ struct ksock_test_env { int rfd; }; +struct ksock_test_case { + const char *name; + bool connect_in_syscall; + bool connect_in_lsm; +}; + static bool ksock_test_env_setup(struct ksock_test_env *env) { struct sockaddr_in addr = { @@ -55,16 +61,18 @@ static bool ksock_test_env_setup(struct ksock_test_env *env) return false; } -void test_ksock_lsm(void) +static void test_ksock_lsm_case(const struct ksock_test_case *test) { LIBBPF_OPTS(bpf_test_run_opts, opts); - struct ksock_test_env env; struct sockaddr_in trigger_addr = { .sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK), }; + struct ksock_test_env env; struct ksock_lsm *skel; char recv_data[sizeof(skel->data->send_data)] = {}; + bool connected = test->connect_in_syscall || test->connect_in_lsm; + int expected_send_ret = connected ? (int)sizeof(skel->data->send_data) : -EDESTADDRREQ; ssize_t n; int tfd = -1; int err; @@ -79,6 +87,8 @@ void test_ksock_lsm(void) /* Step 1: Run the setup SYSCALL prog to create the ksock */ skel->bss->ipv4_remote = htonl(INADDR_LOOPBACK); skel->bss->remote_port = RECV_PORT; + skel->bss->connect_in_syscall = test->connect_in_syscall; + skel->bss->connect_in_lsm = test->connect_in_lsm; err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.ksock_setup), &opts); if (!ASSERT_OK(err, "ksock_setup run")) @@ -103,15 +113,25 @@ void test_ksock_lsm(void) if (!ASSERT_OK(err, "trigger bind")) goto fail; - /* Step 3: Verify the LSM hook sent the notification */ - if (!ASSERT_EQ(skel->data->send_ret, sizeof(skel->data->send_data), - "LSM send bytes")) + /* Step 3: Verify the operations and received notification, if any. */ + if (!ASSERT_EQ(skel->data->connect_ret, connected ? 0 : -1, "connect retval")) + goto fail; + + if (!ASSERT_EQ(skel->data->send_ret, expected_send_ret, "LSM send retval")) + goto fail; + + n = recvfrom(env.rfd, recv_data, sizeof(recv_data), + connected ? 0 : MSG_DONTWAIT, NULL, NULL); + if (!connected) { + if (ASSERT_EQ(n, -1, "no datagram")) + ASSERT_EQ(errno, EAGAIN, "recvfrom errno"); goto fail; + } - n = recvfrom(env.rfd, recv_data, sizeof(recv_data), 0, NULL, NULL); - if (ASSERT_EQ(n, sizeof(recv_data), "recvfrom len")) - ASSERT_MEMEQ(recv_data, skel->data->send_data, sizeof(recv_data), - "payload match"); + if (!ASSERT_EQ(n, sizeof(recv_data), "recvfrom len") || + !ASSERT_MEMEQ(recv_data, skel->data->send_data, sizeof(recv_data), + "payload match")) + goto fail; fail: if (tfd >= 0) @@ -124,6 +144,21 @@ void test_ksock_lsm(void) ksock_lsm__destroy(skel); } +void test_ksock_lsm(void) +{ + static const struct ksock_test_case tests[] = { + { .name = "unconnected_send" }, + { .name = "syscall_connect_send", .connect_in_syscall = true }, + { .name = "lsm_connect_send", .connect_in_lsm = true }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + if (test__start_subtest(tests[i].name)) + test_ksock_lsm_case(&tests[i]); + } +} + void test_ksock_lsm_verifier(void) { RUN_TESTS(ksock_lsm_verifier); diff --git a/tools/testing/selftests/bpf/progs/ksock_lsm.c b/tools/testing/selftests/bpf/progs/ksock_lsm.c index 9808451098ef..944398ce2852 100644 --- a/tools/testing/selftests/bpf/progs/ksock_lsm.c +++ b/tools/testing/selftests/bpf/progs/ksock_lsm.c @@ -13,6 +13,9 @@ char send_data[32] = "hello from bpf ksock"; __be32 ipv4_remote; __u16 remote_port; int target_pid; +bool connect_in_syscall; +bool connect_in_lsm; +int connect_ret = -1; int send_ret = -1; SEC("syscall") @@ -31,14 +34,15 @@ int ksock_setup(void *ctx) if (!ks) return err; - addr.sin.sin_family = AF_INET; - addr.sin.sin_port = bpf_htons(remote_port); - addr.sin.sin_addr.s_addr = ipv4_remote; - - err = bpf_ksock_connect(ks, &addr, sizeof(addr)); - if (err) { - bpf_ksock_release(ks); - return err; + if (connect_in_syscall) { + addr.sin.sin_family = AF_INET; + addr.sin.sin_port = bpf_htons(remote_port); + addr.sin.sin_addr.s_addr = ipv4_remote; + connect_ret = bpf_ksock_connect(ks, &addr, sizeof(addr)); + if (connect_ret) { + bpf_ksock_release(ks); + return connect_ret; + } } err = ksock_ctx_insert(ks); @@ -51,6 +55,7 @@ SEC("lsm.s/socket_bind") int BPF_PROG(ksock_socket_bind, struct socket *sock, struct sockaddr *address, int addrlen, int ret) { + union bpf_ksock_addr addr = {}; struct bpf_ksock *ks; u32 pid = bpf_get_current_pid_tgid() >> 32; @@ -63,7 +68,18 @@ int BPF_PROG(ksock_socket_bind, struct socket *sock, struct sockaddr *address, return ret; } + addr.sin.sin_family = AF_INET; + addr.sin.sin_port = bpf_htons(remote_port); + addr.sin.sin_addr.s_addr = ipv4_remote; + + if (connect_in_lsm) { + connect_ret = bpf_ksock_connect(ks, &addr, sizeof(addr)); + if (connect_ret) + goto out; + } + send_ret = bpf_ksock_send(ks, send_data, sizeof(send_data)); +out: bpf_ksock_release(ks); return ret; -- 2.43.0 The motivation is to plumb more of the socket API into ksock, thus supporting sendto() in addition to send() which makes a lot of sense in the context of UDP sockets which are the only type currently supported. This is useful when ksock is used to reply to specific addresses or send to multiple addresses. Signed-off-by: Mahe Tardy --- net/core/bpf_ksock.c | 98 ++++++++++++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 27 deletions(-) diff --git a/net/core/bpf_ksock.c b/net/core/bpf_ksock.c index 87329c4a2f8e..dda5aefb101e 100644 --- a/net/core/bpf_ksock.c +++ b/net/core/bpf_ksock.c @@ -37,6 +37,49 @@ static void ksock_release_work_fn(struct work_struct *work) kfree(ks); } +static int bpf_ksock_addr_to_sockaddr(const union bpf_ksock_addr *addr, + u32 addr_sz, struct sockaddr_storage *sa) +{ + if (!addr || addr_sz != sizeof(*addr)) + return -EINVAL; + + /* Kfunc memory arguments may be unaligned. */ + memcpy(sa, addr, sizeof(*addr)); + + switch (sa->ss_family) { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + default: + return -EAFNOSUPPORT; + } +} + +static int ksock_sendto(struct bpf_ksock *ks, const void *data, u32 data_sz, + const union bpf_ksock_addr *addr, u32 addr_sz) +{ + struct sockaddr_storage sa; + struct msghdr msg = { + .msg_flags = MSG_DONTWAIT, + }; + struct kvec iov = { + .iov_base = (void *)data, + .iov_len = data_sz, + }; + int addrlen; + + if (addr) { + addrlen = bpf_ksock_addr_to_sockaddr(addr, addr_sz, &sa); + if (addrlen < 0) + return addrlen; + msg.msg_name = &sa; + msg.msg_namelen = addrlen; + } + + return kernel_sendmsg(ks->sock, &msg, &iov, 1, data_sz); +} + __bpf_kfunc_start_defs(); /** @@ -155,22 +198,9 @@ __bpf_kfunc int bpf_ksock_connect(struct bpf_ksock *ks, struct sockaddr_storage sa; int addrlen; - if (!addr || addr__sz != sizeof(*addr)) - return -EINVAL; - - /* Kfunc memory arguments may be unaligned. */ - memcpy(&sa, addr, sizeof(*addr)); - - switch (sa.ss_family) { - case AF_INET: - addrlen = sizeof(struct sockaddr_in); - break; - case AF_INET6: - addrlen = sizeof(struct sockaddr_in6); - break; - default: - return -EAFNOSUPPORT; - } + addrlen = bpf_ksock_addr_to_sockaddr(addr, addr__sz, &sa); + if (addrlen < 0) + return addrlen; return connect_socket(ks->sock, &sa, addrlen, 0); } @@ -225,18 +255,29 @@ CFI_NOSEAL(bpf_ksock_release_dtor); __bpf_kfunc int bpf_ksock_send(struct bpf_ksock *ks, const void *data, u32 data__sz) { - struct msghdr msg = { - .msg_flags = MSG_DONTWAIT, - }; - struct kvec iov = { - .iov_base = (void *)data, - .iov_len = data__sz, - }; - int ret; - - ret = kernel_sendmsg(ks->sock, &msg, &iov, 1, data__sz); + return ksock_sendto(ks, data, data__sz, NULL, 0); +} - return ret; +/** + * bpf_ksock_sendto() - Send data through a BPF kernel socket to a remote address. + * @ks: The BPF kernel socket context. Must be an acquired reference. + * @data: Pointer to the data to send. + * @data__sz: Size of the data to send. + * @addr__nullable: Pointer to an IPv4 or IPv6 socket address, or NULL to use + * the connected destination. + * @addr__sz: Size of the address union, ignored when the address is NULL. + * + * Sends data to the specified address, or to the connected destination when + * the address is NULL, best-effort and nonblocking. This may sleep + * (kernel_sendmsg), so it can only be called from sleepable BPF programs. + * + * Return: Number of bytes sent on success, negative errno on error. + */ +__bpf_kfunc int bpf_ksock_sendto(struct bpf_ksock *ks, const void *data, u32 data__sz, + const union bpf_ksock_addr *addr__nullable, + u32 addr__sz) +{ + return ksock_sendto(ks, data, data__sz, addr__nullable, addr__sz); } __bpf_kfunc_end_defs(); @@ -255,6 +296,7 @@ BTF_ID_FLAGS(func, bpf_ksock_release, KF_RELEASE) BTF_ID_FLAGS(func, bpf_ksock_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NULL) BTF_ID_FLAGS(func, bpf_ksock_connect, KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_ksock_send, KF_SLEEPABLE) +BTF_ID_FLAGS(func, bpf_ksock_sendto, KF_SLEEPABLE) BTF_KFUNCS_END(ksock_kfunc_btf_ids) #ifdef CONFIG_BPF_LSM @@ -265,6 +307,7 @@ BTF_ID(func, bpf_lsm_socket_sendmsg) BTF_ID_UNUSED #endif BTF_ID_LIST_SINGLE(bpf_ksock_send_id, func, bpf_ksock_send) +BTF_ID_LIST_SINGLE(bpf_ksock_sendto_id, func, bpf_ksock_sendto) BTF_ID_LIST(bpf_lsm_socket_connect_id) #ifdef CONFIG_SECURITY_NETWORK @@ -287,6 +330,7 @@ static int bpf_ksock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id) if (prog->type == BPF_PROG_TYPE_LSM) { if (prog->aux->attach_btf_id == bpf_lsm_socket_sendmsg_id[0] && (kfunc_id == bpf_ksock_send_id[0] || + kfunc_id == bpf_ksock_sendto_id[0] || /* * Break the socket_sendmsg -> bpf_ksock_connect() -> * socket_connect -> bpf_ksock_send() -> socket_sendmsg' -- 2.43.0 Similarly to '7b0dfbf5776b ("selftests/bpf: Test forbidden bpf_ksock_send() LSM attach")', tests that the verifier filter prevents bpf_ksock_sendto() to be called in a LSM program attached to security_socket_sendmsg() as it would create a direct recursion. Signed-off-by: Mahe Tardy --- .../selftests/bpf/progs/ksock_lsm_verifier.c | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c b/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c index 7b5d118e5750..82901c9e78c8 100644 --- a/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c +++ b/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c @@ -33,7 +33,26 @@ int BPF_PROG(socket_sendmsg_ksock_send, struct socket *sock, struct msghdr *msg, } SEC("lsm.s/socket_sendmsg") -__description("bpf_ksock operations other than send are allowed from socket_sendmsg LSM hook") +__description("bpf_ksock_sendto is rejected from socket_sendmsg LSM hook") +__failure __msg("calling kernel function bpf_ksock_sendto is not allowed") +int BPF_PROG(socket_sendmsg_ksock_sendto, struct socket *sock, + struct msghdr *msg, int size, int ret) +{ + struct bpf_ksock *ks; + + ks = ksock_ctx_get(); + if (!ks) + return ret; + + bpf_ksock_sendto(ks, send_data, sizeof(send_data), NULL, 0); + bpf_ksock_release(ks); + + return ret; +} + + +SEC("lsm.s/socket_sendmsg") +__description("bpf_ksock acquire and release are allowed from socket_sendmsg LSM hook") __success int BPF_PROG(socket_sendmsg_ksock_allowed, struct socket *sock, struct msghdr *msg, int size, int ret) @@ -103,6 +122,7 @@ int BPF_PROG(socket_connect_ksock_allowed, struct socket *sock, return ret; bpf_ksock_send(ks, send_data, sizeof(send_data)); + bpf_ksock_sendto(ks, send_data, sizeof(send_data), NULL, 0); bpf_ksock_release(ks); return ret; -- 2.43.0 These tests are covering that: 1. sendto() on a unconnected UDP socket works (and that the socket stays unconnected, i.e. send() on it fails with -EDESTADDRREQ). 2. sendto() on a connected UDP socket with a NULL address is equivalent to send(). 3. sendto() on a connected socket with an explicit address doesn't change the peer of the socket (send() after sendto() still sends to the original connected address). Signed-off-by: Mahe Tardy --- .../selftests/bpf/prog_tests/ksock_sendto.c | 108 ++++++++++++++++++ .../selftests/bpf/progs/ksock_common.h | 2 + .../selftests/bpf/progs/ksock_sendto.c | 59 ++++++++++ 3 files changed, 169 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock_sendto.c create mode 100644 tools/testing/selftests/bpf/progs/ksock_sendto.c diff --git a/tools/testing/selftests/bpf/prog_tests/ksock_sendto.c b/tools/testing/selftests/bpf/prog_tests/ksock_sendto.c new file mode 100644 index 000000000000..498f0e1be6ee --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/ksock_sendto.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Isovalent */ + +#include "test_progs.h" +#include "network_helpers.h" +#include "ksock_sendto.skel.h" + +struct sendto_test_case { + const char *name; + bool connected; + bool use_sendto_addr; +}; + +static bool recv_payload(int fd, const void *expected, size_t len) +{ + char buf[64]; + ssize_t n; + + n = recv(fd, buf, sizeof(buf), MSG_TRUNC); + if (!ASSERT_EQ(n, len, "recv length")) + return false; + return ASSERT_MEMEQ(buf, expected, len, "recv payload"); +} + +static void test_sendto_case(const struct sendto_test_case *test) +{ + LIBBPF_OPTS(bpf_test_run_opts, opts); + struct ksock_sendto *skel; + int receivers[2] = { -1, -1 }; + int err, i, port, sendto_fd; + char buf[64]; + ssize_t n; + + skel = ksock_sendto__open_and_load(); + if (!ASSERT_OK_PTR(skel, "ksock_sendto open and load")) + return; + + /* Use separate receivers for the default peer and explicit destination. */ + for (i = 0; i < ARRAY_SIZE(receivers); i++) { + receivers[i] = start_server(AF_INET, SOCK_DGRAM, "127.0.0.1", 0, 1000); + if (!ASSERT_OK_FD(receivers[i], "receiver socket")) + goto out; + } + + port = get_socket_local_port(receivers[0]); + if (!ASSERT_GE(port, 0, "connected port")) + goto out; + skel->bss->connected_port = port; + port = get_socket_local_port(receivers[1]); + if (!ASSERT_GE(port, 0, "sendto port")) + goto out; + skel->bss->sendto_port = port; + skel->bss->connected = test->connected; + skel->bss->use_sendto_addr = test->use_sendto_addr; + + err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.test_sendto), &opts); + if (!ASSERT_OK(err, "run test_sendto") || !ASSERT_OK(opts.retval, "test_sendto retval")) + goto out; + + /* Check sendto() outcome */ + if (!ASSERT_EQ(skel->data->sendto_ret, sizeof(skel->data->sendto_data), "sendto bytes")) + goto out; + sendto_fd = receivers[test->use_sendto_addr ? 1 : 0]; + if (!recv_payload(sendto_fd, skel->data->sendto_data, sizeof(skel->data->sendto_data))) + goto out; + + /* Check send() outcome */ + if (test->connected) { + if (!ASSERT_EQ(skel->data->send_ret, sizeof(skel->data->send_data), "send bytes")) + goto out; + if (!recv_payload(receivers[0], skel->data->send_data, + sizeof(skel->data->send_data))) + goto out; + } else if (!ASSERT_EQ(skel->data->send_ret, -EDESTADDRREQ, + "send after sendto remains unconnected")) { + goto out; + } + + /* Catch unexpected deliveries to either destination. */ + for (i = 0; i < ARRAY_SIZE(receivers); i++) { + n = recv(receivers[i], buf, sizeof(buf), MSG_DONTWAIT); + if (!ASSERT_EQ(n, -1, "no extra datagram") || + !ASSERT_EQ(errno, EAGAIN, "recv errno")) + goto out; + } + +out: + for (i = 0; i < ARRAY_SIZE(receivers); i++) { + if (receivers[i] >= 0) + close(receivers[i]); + } + ksock_sendto__destroy(skel); +} + +void test_ksock_sendto(void) +{ + static const struct sendto_test_case tests[] = { + { .name = "unconnected", .use_sendto_addr = true }, + { .name = "connected_null_addr", .connected = true }, + { .name = "connected_explicit_addr", .connected = true, .use_sendto_addr = true }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + if (test__start_subtest(tests[i].name)) + test_sendto_case(&tests[i]); + } +} diff --git a/tools/testing/selftests/bpf/progs/ksock_common.h b/tools/testing/selftests/bpf/progs/ksock_common.h index 01edaeb9fdd4..2a32cff2fbe9 100644 --- a/tools/testing/selftests/bpf/progs/ksock_common.h +++ b/tools/testing/selftests/bpf/progs/ksock_common.h @@ -16,6 +16,8 @@ int bpf_ksock_connect(struct bpf_ksock *ks, const union bpf_ksock_addr *addr, struct bpf_ksock *bpf_ksock_acquire(struct bpf_ksock *ks) __ksym; void bpf_ksock_release(struct bpf_ksock *ks) __ksym; int bpf_ksock_send(struct bpf_ksock *ks, const void *data, u32 data__sz) __ksym; +int bpf_ksock_sendto(struct bpf_ksock *ks, const void *data, u32 data__sz, + const union bpf_ksock_addr *addr__nullable, u32 addr__sz) __ksym; void bpf_rcu_read_lock(void) __ksym; void bpf_rcu_read_unlock(void) __ksym; diff --git a/tools/testing/selftests/bpf/progs/ksock_sendto.c b/tools/testing/selftests/bpf/progs/ksock_sendto.c new file mode 100644 index 000000000000..b6eacf277fcb --- /dev/null +++ b/tools/testing/selftests/bpf/progs/ksock_sendto.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Isovalent */ + +#include "vmlinux.h" +#include +#include +#include "bpf_tracing_net.h" +#include "ksock_common.h" + +char sendto_data[] = "hello from bpf sendto"; +char send_data[] = "hello from bpf send"; + +__be16 connected_port; +__be16 sendto_port; +bool connected; +bool use_sendto_addr; +int sendto_ret = -1; +int send_ret = -1; + +SEC("syscall") +int test_sendto(void *ctx) +{ + struct bpf_ksock_create_opts opts = { + .family = AF_INET, + .type = SOCK_DGRAM, + .protocol = IPPROTO_UDP, + }; + union bpf_ksock_addr addr = {}; + struct bpf_ksock *ks; + int err = 0; + + ks = bpf_ksock_create(&opts, sizeof(opts), &err); + if (!ks) + return err; + + addr.sin.sin_family = AF_INET; + addr.sin.sin_addr.s_addr = bpf_htonl(0x7f000001); + if (connected) { + addr.sin.sin_port = connected_port; + err = bpf_ksock_connect(ks, &addr, sizeof(addr)); + if (err) + goto out; + } + + addr.sin.sin_port = sendto_port; + if (use_sendto_addr) + sendto_ret = bpf_ksock_sendto(ks, sendto_data, sizeof(sendto_data), + &addr, sizeof(addr)); + else + sendto_ret = bpf_ksock_sendto(ks, sendto_data, sizeof(sendto_data), NULL, 0); + + /* sendto() must neither connect the socket nor change its default peer. */ + send_ret = bpf_ksock_send(ks, send_data, sizeof(send_data)); +out: + bpf_ksock_release(ks); + return err; +} + +char __license[] SEC("license") = "GPL"; -- 2.43.0