Commit 88232ec1 authored by Chuck Lever's avatar Chuck Lever Committed by Jakub Kicinski
Browse files

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: default avatarChuck Lever <chuck.lever@oracle.com>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 2fd55320
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -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
+11 −0
Original line number Diff line number Diff line
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
+2 −0
Original line number Diff line number Diff line
@@ -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
+523 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2023 Oracle and/or its affiliates.
 *
 * KUnit test of the handshake upcall mechanism.
 */

#include <kunit/test.h>
#include <kunit/visibility.h>

#include <linux/kernel.h>

#include <net/sock.h>
#include <net/genetlink.h>
#include <net/netns/generic.h>

#include <uapi/linux/handshake.h>
#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");
+5 −0
Original line number Diff line number Diff line
@@ -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);
Loading