From 3b3009ea8abb713b022d94fba95ec270cf6e7eae Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 17 Apr 2023 10:32:26 -0400 Subject: net/handshake: Create a NETLINK service for handling handshake requests When a kernel consumer needs a transport layer security session, it first needs a handshake to negotiate and establish a session. This negotiation can be done in user space via one of the several existing library implementations, or it can be done in the kernel. No in-kernel handshake implementations yet exist. In their absence, we add a netlink service that can: a. Notify a user space daemon that a handshake is needed. b. Once notified, the daemon calls the kernel back via this netlink service to get the handshake parameters, including an open socket on which to establish the session. c. Once the handshake is complete, the daemon reports the session status and other information via a second netlink operation. This operation marks that it is safe for the kernel to use the open socket and the security session established there. The notification service uses a multicast group. Each handshake mechanism (eg, tlshd) adopts its own group number so that the handshake services are completely independent of one another. The kernel can then tell via netlink_has_listeners() whether a handshake service is active and prepared to handle a handshake request. A new netlink operation, ACCEPT, acts like accept(2) in that it instantiates a file descriptor in the user space daemon's fd table. If this operation is successful, the reply carries the fd number, which can be treated as an open and ready file descriptor. While user space is performing the handshake, the kernel keeps its muddy paws off the open socket. A second new netlink operation, DONE, indicates that the user space daemon is finished with the socket and it is safe for the kernel to use again. The operation also indicates whether a session was established successfully. Signed-off-by: Chuck Lever Signed-off-by: Jakub Kicinski --- net/handshake/request.c | 339 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 net/handshake/request.c (limited to 'net/handshake/request.c') diff --git a/net/handshake/request.c b/net/handshake/request.c new file mode 100644 index 000000000000..d5b2bc6de057 --- /dev/null +++ b/net/handshake/request.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Handshake request lifetime events + * + * Author: Chuck Lever + * + * Copyright (c) 2023, Oracle and/or its affiliates. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "handshake.h" + +#include + +/* + * We need both a handshake_req -> sock mapping, and a sock -> + * handshake_req mapping. Both are one-to-one. + * + * To avoid adding another pointer field to struct sock, net/handshake + * maintains a hash table, indexed by the memory address of @sock, to + * find the struct handshake_req outstanding for that socket. The + * reverse direction uses a simple pointer field in the handshake_req + * struct. + */ + +static struct rhashtable handshake_rhashtbl ____cacheline_aligned_in_smp; + +static const struct rhashtable_params handshake_rhash_params = { + .key_len = sizeof_field(struct handshake_req, hr_sk), + .key_offset = offsetof(struct handshake_req, hr_sk), + .head_offset = offsetof(struct handshake_req, hr_rhash), + .automatic_shrinking = true, +}; + +int handshake_req_hash_init(void) +{ + return rhashtable_init(&handshake_rhashtbl, &handshake_rhash_params); +} + +void handshake_req_hash_destroy(void) +{ + rhashtable_destroy(&handshake_rhashtbl); +} + +struct handshake_req *handshake_req_hash_lookup(struct sock *sk) +{ + return rhashtable_lookup_fast(&handshake_rhashtbl, &sk, + handshake_rhash_params); +} + +static bool handshake_req_hash_add(struct handshake_req *req) +{ + int ret; + + ret = rhashtable_lookup_insert_fast(&handshake_rhashtbl, + &req->hr_rhash, + handshake_rhash_params); + return ret == 0; +} + +static void handshake_req_destroy(struct handshake_req *req) +{ + if (req->hr_proto->hp_destroy) + req->hr_proto->hp_destroy(req); + rhashtable_remove_fast(&handshake_rhashtbl, &req->hr_rhash, + handshake_rhash_params); + kfree(req); +} + +static void handshake_sk_destruct(struct sock *sk) +{ + void (*sk_destruct)(struct sock *sk); + struct handshake_req *req; + + req = handshake_req_hash_lookup(sk); + if (!req) + return; + + trace_handshake_destruct(sock_net(sk), req, sk); + sk_destruct = req->hr_odestruct; + handshake_req_destroy(req); + if (sk_destruct) + sk_destruct(sk); +} + +/** + * handshake_req_alloc - Allocate a handshake request + * @proto: security protocol + * @flags: memory allocation flags + * + * Returns an initialized handshake_req or NULL. + */ +struct handshake_req *handshake_req_alloc(const struct handshake_proto *proto, + gfp_t flags) +{ + struct handshake_req *req; + + if (!proto) + return NULL; + if (proto->hp_handler_class <= HANDSHAKE_HANDLER_CLASS_NONE) + return NULL; + if (proto->hp_handler_class >= HANDSHAKE_HANDLER_CLASS_MAX) + return NULL; + if (!proto->hp_accept || !proto->hp_done) + return NULL; + + req = kzalloc(struct_size(req, hr_priv, proto->hp_privsize), flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->hr_list); + req->hr_proto = proto; + return req; +} +EXPORT_SYMBOL(handshake_req_alloc); + +/** + * handshake_req_private - Get per-handshake private data + * @req: handshake arguments + * + */ +void *handshake_req_private(struct handshake_req *req) +{ + return (void *)&req->hr_priv; +} +EXPORT_SYMBOL(handshake_req_private); + +static bool __add_pending_locked(struct handshake_net *hn, + struct handshake_req *req) +{ + if (WARN_ON_ONCE(!list_empty(&req->hr_list))) + return false; + hn->hn_pending++; + list_add_tail(&req->hr_list, &hn->hn_requests); + return true; +} + +static void __remove_pending_locked(struct handshake_net *hn, + struct handshake_req *req) +{ + hn->hn_pending--; + list_del_init(&req->hr_list); +} + +/* + * Returns %true if the request was found on @net's pending list, + * otherwise %false. + * + * If @req was on a pending list, it has not yet been accepted. + */ +static bool remove_pending(struct handshake_net *hn, struct handshake_req *req) +{ + bool ret = false; + + spin_lock(&hn->hn_lock); + if (!list_empty(&req->hr_list)) { + __remove_pending_locked(hn, req); + ret = true; + } + spin_unlock(&hn->hn_lock); + + return ret; +} + +struct handshake_req *handshake_req_next(struct handshake_net *hn, int class) +{ + struct handshake_req *req, *pos; + + req = NULL; + spin_lock(&hn->hn_lock); + list_for_each_entry(pos, &hn->hn_requests, hr_list) { + if (pos->hr_proto->hp_handler_class != class) + continue; + __remove_pending_locked(hn, pos); + req = pos; + break; + } + spin_unlock(&hn->hn_lock); + + return req; +} + +/** + * handshake_req_submit - Submit a handshake request + * @sock: open socket on which to perform the handshake + * @req: handshake arguments + * @flags: memory allocation flags + * + * Return values: + * %0: Request queued + * %-EINVAL: Invalid argument + * %-EBUSY: A handshake is already under way for this socket + * %-ESRCH: No handshake agent is available + * %-EAGAIN: Too many pending handshake requests + * %-ENOMEM: Failed to allocate memory + * %-EMSGSIZE: Failed to construct notification message + * %-EOPNOTSUPP: Handshake module not initialized + * + * A zero return value from handshake_req_submit() means that + * exactly one subsequent completion callback is guaranteed. + * + * A negative return value from handshake_req_submit() means that + * no completion callback will be done and that @req has been + * destroyed. + */ +int handshake_req_submit(struct socket *sock, struct handshake_req *req, + gfp_t flags) +{ + struct handshake_net *hn; + struct net *net; + int ret; + + if (!sock || !req || !sock->file) { + kfree(req); + return -EINVAL; + } + + req->hr_sk = sock->sk; + if (!req->hr_sk) { + kfree(req); + return -EINVAL; + } + req->hr_odestruct = req->hr_sk->sk_destruct; + req->hr_sk->sk_destruct = handshake_sk_destruct; + + ret = -EOPNOTSUPP; + net = sock_net(req->hr_sk); + hn = handshake_pernet(net); + if (!hn) + goto out_err; + + ret = -EAGAIN; + if (READ_ONCE(hn->hn_pending) >= hn->hn_pending_max) + goto out_err; + + spin_lock(&hn->hn_lock); + ret = -EOPNOTSUPP; + if (test_bit(HANDSHAKE_F_NET_DRAINING, &hn->hn_flags)) + goto out_unlock; + ret = -EBUSY; + if (!handshake_req_hash_add(req)) + goto out_unlock; + if (!__add_pending_locked(hn, req)) + goto out_unlock; + spin_unlock(&hn->hn_lock); + + ret = handshake_genl_notify(net, req->hr_proto, flags); + if (ret) { + trace_handshake_notify_err(net, req, req->hr_sk, ret); + if (remove_pending(hn, req)) + goto out_err; + } + + /* Prevent socket release while a handshake request is pending */ + sock_hold(req->hr_sk); + + trace_handshake_submit(net, req, req->hr_sk); + return 0; + +out_unlock: + spin_unlock(&hn->hn_lock); +out_err: + trace_handshake_submit_err(net, req, req->hr_sk, ret); + handshake_req_destroy(req); + return ret; +} +EXPORT_SYMBOL(handshake_req_submit); + +void handshake_complete(struct handshake_req *req, unsigned int status, + struct genl_info *info) +{ + struct sock *sk = req->hr_sk; + struct net *net = sock_net(sk); + + if (!test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) { + trace_handshake_complete(net, req, sk, status); + req->hr_proto->hp_done(req, status, info); + + /* Handshake request is no longer pending */ + sock_put(sk); + } +} + +/** + * handshake_req_cancel - Cancel an in-progress handshake + * @sk: socket on which there is an ongoing handshake + * + * Request cancellation races with request completion. To determine + * who won, callers examine the return value from this function. + * + * Return values: + * %true - Uncompleted handshake request was canceled + * %false - Handshake request already completed or not found + */ +bool handshake_req_cancel(struct sock *sk) +{ + struct handshake_req *req; + struct handshake_net *hn; + struct net *net; + + net = sock_net(sk); + req = handshake_req_hash_lookup(sk); + if (!req) { + trace_handshake_cancel_none(net, req, sk); + return false; + } + + hn = handshake_pernet(net); + if (hn && remove_pending(hn, req)) { + /* Request hadn't been accepted */ + goto out_true; + } + if (test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) { + /* Request already completed */ + trace_handshake_cancel_busy(net, req, sk); + return false; + } + +out_true: + trace_handshake_cancel(net, req, sk); + + /* Handshake request is no longer pending */ + sock_put(sk); + return true; +} +EXPORT_SYMBOL(handshake_req_cancel); -- cgit v1.2.3 From 88232ec1ec5ecf4aa5de439cff3d5e2b7adcac93 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 17 Apr 2023 10:32:39 -0400 Subject: net/handshake: Add Kunit tests for the handshake consumer API These verify the API contracts and help exercise lifetime rules for consumer sockets and handshake_req structures. One way to run these tests: ./tools/testing/kunit/kunit.py run --kunitconfig ./net/handshake/.kunitconfig Signed-off-by: Chuck Lever Signed-off-by: Jakub Kicinski --- net/Kconfig | 15 ++ net/handshake/.kunitconfig | 11 + net/handshake/Makefile | 2 + net/handshake/handshake-test.c | 523 +++++++++++++++++++++++++++++++++++++++++ net/handshake/handshake.h | 5 + net/handshake/netlink.c | 7 + net/handshake/request.c | 5 + net/handshake/tlshd.c | 1 + 8 files changed, 569 insertions(+) create mode 100644 net/handshake/.kunitconfig create mode 100644 net/handshake/handshake-test.c (limited to 'net/handshake/request.c') diff --git a/net/Kconfig b/net/Kconfig index 4b800706cc76..7d39c1773eb4 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -73,6 +73,21 @@ config NET_HANDSHAKE depends on SUNRPC || NVME_TARGET_TCP || NVME_TCP default y +config NET_HANDSHAKE_KUNIT_TEST + tristate "KUnit tests for the handshake upcall mechanism" if !KUNIT_ALL_TESTS + default KUNIT_ALL_TESTS + depends on KUNIT + help + This builds the KUnit tests for the handshake upcall mechanism. + + KUnit tests run during boot and output the results to the debug + log in TAP format (https://testanything.org/). Only useful for + kernel devs running KUnit test harness and are not for inclusion + into a production build. + + For more information on KUnit and unit tests in general, refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + config INET bool "TCP/IP networking" help diff --git a/net/handshake/.kunitconfig b/net/handshake/.kunitconfig new file mode 100644 index 000000000000..5c48cf4abca2 --- /dev/null +++ b/net/handshake/.kunitconfig @@ -0,0 +1,11 @@ +CONFIG_KUNIT=y +CONFIG_UBSAN=y +CONFIG_STACKTRACE=y +CONFIG_NET=y +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_INET=y +CONFIG_MULTIUSER=y +CONFIG_NFS_FS=y +CONFIG_SUNRPC=y +CONFIG_NET_HANDSHAKE=y +CONFIG_NET_HANDSHAKE_KUNIT_TEST=y diff --git a/net/handshake/Makefile b/net/handshake/Makefile index a089f7e3df24..247d73c6ff6e 100644 --- a/net/handshake/Makefile +++ b/net/handshake/Makefile @@ -9,3 +9,5 @@ obj-y += handshake.o handshake-y := genl.o netlink.o request.o tlshd.o trace.o + +obj-$(CONFIG_NET_HANDSHAKE_KUNIT_TEST) += handshake-test.o diff --git a/net/handshake/handshake-test.c b/net/handshake/handshake-test.c new file mode 100644 index 000000000000..e6adc5dec11a --- /dev/null +++ b/net/handshake/handshake-test.c @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * KUnit test of the handshake upcall mechanism. + */ + +#include +#include + +#include + +#include +#include +#include + +#include +#include "handshake.h" + +MODULE_IMPORT_NS(EXPORTED_FOR_KUNIT_TESTING); + +static int test_accept_func(struct handshake_req *req, struct genl_info *info, + int fd) +{ + return 0; +} + +static void test_done_func(struct handshake_req *req, unsigned int status, + struct genl_info *info) +{ +} + +struct handshake_req_alloc_test_param { + const char *desc; + struct handshake_proto *proto; + gfp_t gfp; + bool expect_success; +}; + +static struct handshake_proto handshake_req_alloc_proto_2 = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_NONE, +}; + +static struct handshake_proto handshake_req_alloc_proto_3 = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_MAX, +}; + +static struct handshake_proto handshake_req_alloc_proto_4 = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_TLSHD, +}; + +static struct handshake_proto handshake_req_alloc_proto_5 = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_TLSHD, + .hp_accept = test_accept_func, +}; + +static struct handshake_proto handshake_req_alloc_proto_6 = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_TLSHD, + .hp_privsize = UINT_MAX, + .hp_accept = test_accept_func, + .hp_done = test_done_func, +}; + +static struct handshake_proto handshake_req_alloc_proto_good = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_TLSHD, + .hp_accept = test_accept_func, + .hp_done = test_done_func, +}; + +static const +struct handshake_req_alloc_test_param handshake_req_alloc_params[] = { + { + .desc = "handshake_req_alloc NULL proto", + .proto = NULL, + .gfp = GFP_KERNEL, + .expect_success = false, + }, + { + .desc = "handshake_req_alloc CLASS_NONE", + .proto = &handshake_req_alloc_proto_2, + .gfp = GFP_KERNEL, + .expect_success = false, + }, + { + .desc = "handshake_req_alloc CLASS_MAX", + .proto = &handshake_req_alloc_proto_3, + .gfp = GFP_KERNEL, + .expect_success = false, + }, + { + .desc = "handshake_req_alloc no callbacks", + .proto = &handshake_req_alloc_proto_4, + .gfp = GFP_KERNEL, + .expect_success = false, + }, + { + .desc = "handshake_req_alloc no done callback", + .proto = &handshake_req_alloc_proto_5, + .gfp = GFP_KERNEL, + .expect_success = false, + }, + { + .desc = "handshake_req_alloc excessive privsize", + .proto = &handshake_req_alloc_proto_6, + .gfp = GFP_KERNEL, + .expect_success = false, + }, + { + .desc = "handshake_req_alloc all good", + .proto = &handshake_req_alloc_proto_good, + .gfp = GFP_KERNEL, + .expect_success = true, + }, +}; + +static void +handshake_req_alloc_get_desc(const struct handshake_req_alloc_test_param *param, + char *desc) +{ + strscpy(desc, param->desc, KUNIT_PARAM_DESC_SIZE); +} + +/* Creates the function handshake_req_alloc_gen_params */ +KUNIT_ARRAY_PARAM(handshake_req_alloc, handshake_req_alloc_params, + handshake_req_alloc_get_desc); + +static void handshake_req_alloc_case(struct kunit *test) +{ + const struct handshake_req_alloc_test_param *param = test->param_value; + struct handshake_req *result; + + /* Arrange */ + + /* Act */ + result = handshake_req_alloc(param->proto, param->gfp); + + /* Assert */ + if (param->expect_success) + KUNIT_EXPECT_NOT_NULL(test, result); + else + KUNIT_EXPECT_NULL(test, result); + + kfree(result); +} + +static void handshake_req_submit_test1(struct kunit *test) +{ + struct socket *sock; + int err, result; + + /* Arrange */ + err = __sock_create(&init_net, PF_INET, SOCK_STREAM, IPPROTO_TCP, + &sock, 1); + KUNIT_ASSERT_EQ(test, err, 0); + + /* Act */ + result = handshake_req_submit(sock, NULL, GFP_KERNEL); + + /* Assert */ + KUNIT_EXPECT_EQ(test, result, -EINVAL); + + sock_release(sock); +} + +static void handshake_req_submit_test2(struct kunit *test) +{ + struct handshake_req *req; + int result; + + /* Arrange */ + req = handshake_req_alloc(&handshake_req_alloc_proto_good, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, req); + + /* Act */ + result = handshake_req_submit(NULL, req, GFP_KERNEL); + + /* Assert */ + KUNIT_EXPECT_EQ(test, result, -EINVAL); + + /* handshake_req_submit() destroys @req on error */ +} + +static void handshake_req_submit_test3(struct kunit *test) +{ + struct handshake_req *req; + struct socket *sock; + int err, result; + + /* Arrange */ + req = handshake_req_alloc(&handshake_req_alloc_proto_good, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, req); + + err = __sock_create(&init_net, PF_INET, SOCK_STREAM, IPPROTO_TCP, + &sock, 1); + KUNIT_ASSERT_EQ(test, err, 0); + sock->file = NULL; + + /* Act */ + result = handshake_req_submit(sock, req, GFP_KERNEL); + + /* Assert */ + KUNIT_EXPECT_EQ(test, result, -EINVAL); + + /* handshake_req_submit() destroys @req on error */ + sock_release(sock); +} + +static void handshake_req_submit_test4(struct kunit *test) +{ + struct handshake_req *req, *result; + struct socket *sock; + int err; + + /* Arrange */ + req = handshake_req_alloc(&handshake_req_alloc_proto_good, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, req); + + err = __sock_create(&init_net, PF_INET, SOCK_STREAM, IPPROTO_TCP, + &sock, 1); + KUNIT_ASSERT_EQ(test, err, 0); + sock->file = sock_alloc_file(sock, O_NONBLOCK, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sock->file); + KUNIT_ASSERT_NOT_NULL(test, sock->sk); + + err = handshake_req_submit(sock, req, GFP_KERNEL); + KUNIT_ASSERT_EQ(test, err, 0); + + /* Act */ + result = handshake_req_hash_lookup(sock->sk); + + /* Assert */ + KUNIT_EXPECT_NOT_NULL(test, result); + KUNIT_EXPECT_PTR_EQ(test, req, result); + + handshake_req_cancel(sock->sk); + sock_release(sock); +} + +static void handshake_req_submit_test5(struct kunit *test) +{ + struct handshake_req *req; + struct handshake_net *hn; + struct socket *sock; + struct net *net; + int saved, err; + + /* Arrange */ + req = handshake_req_alloc(&handshake_req_alloc_proto_good, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, req); + + err = __sock_create(&init_net, PF_INET, SOCK_STREAM, IPPROTO_TCP, + &sock, 1); + KUNIT_ASSERT_EQ(test, err, 0); + sock->file = sock_alloc_file(sock, O_NONBLOCK, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sock->file); + KUNIT_ASSERT_NOT_NULL(test, sock->sk); + + net = sock_net(sock->sk); + hn = handshake_pernet(net); + KUNIT_ASSERT_NOT_NULL(test, hn); + + saved = hn->hn_pending; + hn->hn_pending = hn->hn_pending_max + 1; + + /* Act */ + err = handshake_req_submit(sock, req, GFP_KERNEL); + + /* Assert */ + KUNIT_EXPECT_EQ(test, err, -EAGAIN); + + sock_release(sock); + hn->hn_pending = saved; +} + +static void handshake_req_submit_test6(struct kunit *test) +{ + struct handshake_req *req1, *req2; + struct socket *sock; + int err; + + /* Arrange */ + req1 = handshake_req_alloc(&handshake_req_alloc_proto_good, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, req1); + req2 = handshake_req_alloc(&handshake_req_alloc_proto_good, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, req2); + + err = __sock_create(&init_net, PF_INET, SOCK_STREAM, IPPROTO_TCP, + &sock, 1); + KUNIT_ASSERT_EQ(test, err, 0); + sock->file = sock_alloc_file(sock, O_NONBLOCK, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sock->file); + KUNIT_ASSERT_NOT_NULL(test, sock->sk); + + /* Act */ + err = handshake_req_submit(sock, req1, GFP_KERNEL); + KUNIT_ASSERT_EQ(test, err, 0); + err = handshake_req_submit(sock, req2, GFP_KERNEL); + + /* Assert */ + KUNIT_EXPECT_EQ(test, err, -EBUSY); + + handshake_req_cancel(sock->sk); + sock_release(sock); +} + +static void handshake_req_cancel_test1(struct kunit *test) +{ + struct handshake_req *req; + struct socket *sock; + bool result; + int err; + + /* Arrange */ + req = handshake_req_alloc(&handshake_req_alloc_proto_good, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, req); + + err = __sock_create(&init_net, PF_INET, SOCK_STREAM, IPPROTO_TCP, + &sock, 1); + KUNIT_ASSERT_EQ(test, err, 0); + + sock->file = sock_alloc_file(sock, O_NONBLOCK, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sock->file); + + err = handshake_req_submit(sock, req, GFP_KERNEL); + KUNIT_ASSERT_EQ(test, err, 0); + + /* NB: handshake_req hasn't been accepted */ + + /* Act */ + result = handshake_req_cancel(sock->sk); + + /* Assert */ + KUNIT_EXPECT_TRUE(test, result); + + sock_release(sock); +} + +static void handshake_req_cancel_test2(struct kunit *test) +{ + struct handshake_req *req, *next; + struct handshake_net *hn; + struct socket *sock; + struct net *net; + bool result; + int err; + + /* Arrange */ + req = handshake_req_alloc(&handshake_req_alloc_proto_good, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, req); + + err = __sock_create(&init_net, PF_INET, SOCK_STREAM, IPPROTO_TCP, + &sock, 1); + KUNIT_ASSERT_EQ(test, err, 0); + + sock->file = sock_alloc_file(sock, O_NONBLOCK, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sock->file); + + err = handshake_req_submit(sock, req, GFP_KERNEL); + KUNIT_ASSERT_EQ(test, err, 0); + + net = sock_net(sock->sk); + hn = handshake_pernet(net); + KUNIT_ASSERT_NOT_NULL(test, hn); + + /* Pretend to accept this request */ + next = handshake_req_next(hn, HANDSHAKE_HANDLER_CLASS_TLSHD); + KUNIT_ASSERT_PTR_EQ(test, req, next); + + /* Act */ + result = handshake_req_cancel(sock->sk); + + /* Assert */ + KUNIT_EXPECT_TRUE(test, result); + + sock_release(sock); +} + +static void handshake_req_cancel_test3(struct kunit *test) +{ + struct handshake_req *req, *next; + struct handshake_net *hn; + struct socket *sock; + struct net *net; + bool result; + int err; + + /* Arrange */ + req = handshake_req_alloc(&handshake_req_alloc_proto_good, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, req); + + err = __sock_create(&init_net, PF_INET, SOCK_STREAM, IPPROTO_TCP, + &sock, 1); + KUNIT_ASSERT_EQ(test, err, 0); + + sock->file = sock_alloc_file(sock, O_NONBLOCK, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sock->file); + + err = handshake_req_submit(sock, req, GFP_KERNEL); + KUNIT_ASSERT_EQ(test, err, 0); + + net = sock_net(sock->sk); + hn = handshake_pernet(net); + KUNIT_ASSERT_NOT_NULL(test, hn); + + /* Pretend to accept this request */ + next = handshake_req_next(hn, HANDSHAKE_HANDLER_CLASS_TLSHD); + KUNIT_ASSERT_PTR_EQ(test, req, next); + + /* Pretend to complete this request */ + handshake_complete(next, -ETIMEDOUT, NULL); + + /* Act */ + result = handshake_req_cancel(sock->sk); + + /* Assert */ + KUNIT_EXPECT_FALSE(test, result); + + sock_release(sock); +} + +static struct handshake_req *handshake_req_destroy_test; + +static void test_destroy_func(struct handshake_req *req) +{ + handshake_req_destroy_test = req; +} + +static struct handshake_proto handshake_req_alloc_proto_destroy = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_TLSHD, + .hp_accept = test_accept_func, + .hp_done = test_done_func, + .hp_destroy = test_destroy_func, +}; + +static void handshake_req_destroy_test1(struct kunit *test) +{ + struct handshake_req *req; + struct socket *sock; + int err; + + /* Arrange */ + handshake_req_destroy_test = NULL; + + req = handshake_req_alloc(&handshake_req_alloc_proto_destroy, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, req); + + err = __sock_create(&init_net, PF_INET, SOCK_STREAM, IPPROTO_TCP, + &sock, 1); + KUNIT_ASSERT_EQ(test, err, 0); + + sock->file = sock_alloc_file(sock, O_NONBLOCK, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sock->file); + + err = handshake_req_submit(sock, req, GFP_KERNEL); + KUNIT_ASSERT_EQ(test, err, 0); + + handshake_req_cancel(sock->sk); + + /* Act */ + sock_release(sock); + + /* Assert */ + KUNIT_EXPECT_PTR_EQ(test, handshake_req_destroy_test, req); +} + +static struct kunit_case handshake_api_test_cases[] = { + { + .name = "req_alloc API fuzzing", + .run_case = handshake_req_alloc_case, + .generate_params = handshake_req_alloc_gen_params, + }, + { + .name = "req_submit NULL req arg", + .run_case = handshake_req_submit_test1, + }, + { + .name = "req_submit NULL sock arg", + .run_case = handshake_req_submit_test2, + }, + { + .name = "req_submit NULL sock->file", + .run_case = handshake_req_submit_test3, + }, + { + .name = "req_lookup works", + .run_case = handshake_req_submit_test4, + }, + { + .name = "req_submit max pending", + .run_case = handshake_req_submit_test5, + }, + { + .name = "req_submit multiple", + .run_case = handshake_req_submit_test6, + }, + { + .name = "req_cancel before accept", + .run_case = handshake_req_cancel_test1, + }, + { + .name = "req_cancel after accept", + .run_case = handshake_req_cancel_test2, + }, + { + .name = "req_cancel after done", + .run_case = handshake_req_cancel_test3, + }, + { + .name = "req_destroy works", + .run_case = handshake_req_destroy_test1, + }, + {} +}; + +static struct kunit_suite handshake_api_suite = { + .name = "Handshake API tests", + .test_cases = handshake_api_test_cases, +}; + +kunit_test_suites(&handshake_api_suite); + +MODULE_DESCRIPTION("Test handshake upcall API functions"); +MODULE_LICENSE("GPL"); diff --git a/net/handshake/handshake.h b/net/handshake/handshake.h index 52568dbe24f1..4dac965c99df 100644 --- a/net/handshake/handshake.h +++ b/net/handshake/handshake.h @@ -49,6 +49,7 @@ enum hr_flags_bits { struct handshake_proto { int hp_handler_class; size_t hp_privsize; + unsigned long hp_flags; int (*hp_accept)(struct handshake_req *req, struct genl_info *info, int fd); @@ -58,6 +59,10 @@ struct handshake_proto { void (*hp_destroy)(struct handshake_req *req); }; +enum hp_flags_bits { + HANDSHAKE_F_PROTO_NOTIFY, +}; + /* netlink.c */ int handshake_genl_notify(struct net *net, const struct handshake_proto *proto, gfp_t flags); diff --git a/net/handshake/netlink.c b/net/handshake/netlink.c index 7264cac04047..8ea0ff993f9f 100644 --- a/net/handshake/netlink.c +++ b/net/handshake/netlink.c @@ -18,6 +18,8 @@ #include #include +#include + #include #include "handshake.h" #include "genl.h" @@ -38,6 +40,10 @@ int handshake_genl_notify(struct net *net, const struct handshake_proto *proto, struct sk_buff *msg; void *hdr; + /* Disable notifications during unit testing */ + if (!test_bit(HANDSHAKE_F_PROTO_NOTIFY, &proto->hp_flags)) + return 0; + if (!genl_has_listeners(&handshake_nl_family, net, proto->hp_handler_class)) return -ESRCH; @@ -262,6 +268,7 @@ struct handshake_net *handshake_pernet(struct net *net) return handshake_net_id ? net_generic(net, handshake_net_id) : NULL; } +EXPORT_SYMBOL_IF_KUNIT(handshake_pernet); static int __init handshake_init(void) { diff --git a/net/handshake/request.c b/net/handshake/request.c index d5b2bc6de057..94d5cef3e048 100644 --- a/net/handshake/request.c +++ b/net/handshake/request.c @@ -20,6 +20,8 @@ #include #include +#include + #include #include "handshake.h" @@ -60,6 +62,7 @@ struct handshake_req *handshake_req_hash_lookup(struct sock *sk) return rhashtable_lookup_fast(&handshake_rhashtbl, &sk, handshake_rhash_params); } +EXPORT_SYMBOL_IF_KUNIT(handshake_req_hash_lookup); static bool handshake_req_hash_add(struct handshake_req *req) { @@ -192,6 +195,7 @@ struct handshake_req *handshake_req_next(struct handshake_net *hn, int class) return req; } +EXPORT_SYMBOL_IF_KUNIT(handshake_req_next); /** * handshake_req_submit - Submit a handshake request @@ -293,6 +297,7 @@ void handshake_complete(struct handshake_req *req, unsigned int status, sock_put(sk); } } +EXPORT_SYMBOL_IF_KUNIT(handshake_complete); /** * handshake_req_cancel - Cancel an in-progress handshake diff --git a/net/handshake/tlshd.c b/net/handshake/tlshd.c index 1b8353296060..fcbeb63b4eb1 100644 --- a/net/handshake/tlshd.c +++ b/net/handshake/tlshd.c @@ -249,6 +249,7 @@ out: static const struct handshake_proto tls_handshake_proto = { .hp_handler_class = HANDSHAKE_HANDLER_CLASS_TLSHD, .hp_privsize = sizeof(struct tls_handshake_req), + .hp_flags = BIT(HANDSHAKE_F_PROTO_NOTIFY), .hp_accept = tls_handshake_accept, .hp_done = tls_handshake_done, -- cgit v1.2.3