Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+11
View File
@@ -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
+13
View File
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for the Generic HANDSHAKE service
#
# Author: Chuck Lever <chuck.lever@oracle.com>
#
# Copyright (c) 2023, Oracle and/or its affiliates.
#
obj-y += handshake.o
handshake-y := alert.o genl.o netlink.o request.o tlshd.o trace.o
obj-$(CONFIG_NET_HANDSHAKE_KUNIT_TEST) += handshake-test.o
+110
View File
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Handle the TLS Alert protocol
*
* Author: Chuck Lever <chuck.lever@oracle.com>
*
* Copyright (c) 2023, Oracle and/or its affiliates.
*/
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/inet.h>
#include <net/sock.h>
#include <net/handshake.h>
#include <net/tls.h>
#include <net/tls_prot.h>
#include "handshake.h"
#include <trace/events/handshake.h>
/**
* tls_alert_send - send a TLS Alert on a kTLS socket
* @sock: open kTLS socket to send on
* @level: TLS Alert level
* @description: TLS Alert description
*
* Returns zero on success or a negative errno.
*/
int tls_alert_send(struct socket *sock, u8 level, u8 description)
{
u8 record_type = TLS_RECORD_TYPE_ALERT;
u8 buf[CMSG_SPACE(sizeof(record_type))];
struct msghdr msg = { 0 };
struct cmsghdr *cmsg;
struct kvec iov;
u8 alert[2];
int ret;
trace_tls_alert_send(sock->sk, level, description);
alert[0] = level;
alert[1] = description;
iov.iov_base = alert;
iov.iov_len = sizeof(alert);
memset(buf, 0, sizeof(buf));
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
msg.msg_flags = MSG_DONTWAIT;
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_TLS;
cmsg->cmsg_type = TLS_SET_RECORD_TYPE;
cmsg->cmsg_len = CMSG_LEN(sizeof(record_type));
memcpy(CMSG_DATA(cmsg), &record_type, sizeof(record_type));
iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &iov, 1, iov.iov_len);
ret = sock_sendmsg(sock, &msg);
return ret < 0 ? ret : 0;
}
/**
* tls_get_record_type - Look for TLS RECORD_TYPE information
* @sk: socket (for IP address information)
* @cmsg: incoming message to be parsed
*
* Returns zero or a TLS_RECORD_TYPE value.
*/
u8 tls_get_record_type(const struct sock *sk, const struct cmsghdr *cmsg)
{
u8 record_type;
if (cmsg->cmsg_level != SOL_TLS)
return 0;
if (cmsg->cmsg_type != TLS_GET_RECORD_TYPE)
return 0;
record_type = *((u8 *)CMSG_DATA(cmsg));
trace_tls_contenttype(sk, record_type);
return record_type;
}
EXPORT_SYMBOL(tls_get_record_type);
/**
* tls_alert_recv - Parse TLS Alert messages
* @sk: socket (for IP address information)
* @msg: incoming message to be parsed
* @level: OUT - TLS AlertLevel value
* @description: OUT - TLS AlertDescription value
*
*/
void tls_alert_recv(const struct sock *sk, const struct msghdr *msg,
u8 *level, u8 *description)
{
const struct kvec *iov;
u8 *data;
iov = msg->msg_iter.kvec;
data = iov->iov_base;
*level = data[0];
*description = data[1];
trace_tls_alert_recv(sk, *level, *description);
}
EXPORT_SYMBOL(tls_alert_recv);
+60
View File
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
/* Do not edit directly, auto-generated from: */
/* Documentation/netlink/specs/handshake.yaml */
/* YNL-GEN kernel source */
/* To regenerate run: tools/net/ynl/ynl-regen.sh */
#include <net/netlink.h>
#include <net/genetlink.h>
#include "genl.h"
#include <uapi/linux/handshake.h>
#include <linux/err.h>
/* HANDSHAKE_CMD_ACCEPT - do */
static const struct nla_policy handshake_accept_nl_policy[HANDSHAKE_A_ACCEPT_HANDLER_CLASS + 1] = {
[HANDSHAKE_A_ACCEPT_HANDLER_CLASS] = NLA_POLICY_MAX(NLA_U32, 2),
};
/* HANDSHAKE_CMD_DONE - do */
static const struct nla_policy handshake_done_nl_policy[HANDSHAKE_A_DONE_REMOTE_AUTH + 1] = {
[HANDSHAKE_A_DONE_STATUS] = NLA_POLICY_MAX(NLA_U32, MAX_ERRNO),
[HANDSHAKE_A_DONE_SOCKFD] = { .type = NLA_S32, },
[HANDSHAKE_A_DONE_REMOTE_AUTH] = { .type = NLA_U32, },
};
/* Ops table for handshake */
static const struct genl_split_ops handshake_nl_ops[] = {
{
.cmd = HANDSHAKE_CMD_ACCEPT,
.doit = handshake_nl_accept_doit,
.policy = handshake_accept_nl_policy,
.maxattr = HANDSHAKE_A_ACCEPT_HANDLER_CLASS,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = HANDSHAKE_CMD_DONE,
.doit = handshake_nl_done_doit,
.policy = handshake_done_nl_policy,
.maxattr = HANDSHAKE_A_DONE_REMOTE_AUTH,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
};
static const struct genl_multicast_group handshake_nl_mcgrps[] = {
[HANDSHAKE_NLGRP_NONE] = { "none", },
[HANDSHAKE_NLGRP_TLSHD] = { "tlshd", },
};
struct genl_family handshake_nl_family __ro_after_init = {
.name = HANDSHAKE_FAMILY_NAME,
.version = HANDSHAKE_FAMILY_VERSION,
.netnsok = true,
.parallel_ops = true,
.module = THIS_MODULE,
.split_ops = handshake_nl_ops,
.n_split_ops = ARRAY_SIZE(handshake_nl_ops),
.mcgrps = handshake_nl_mcgrps,
.n_mcgrps = ARRAY_SIZE(handshake_nl_mcgrps),
};
+26
View File
@@ -0,0 +1,26 @@
/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
/* Do not edit directly, auto-generated from: */
/* Documentation/netlink/specs/handshake.yaml */
/* YNL-GEN kernel header */
/* To regenerate run: tools/net/ynl/ynl-regen.sh */
#ifndef _LINUX_HANDSHAKE_GEN_H
#define _LINUX_HANDSHAKE_GEN_H
#include <net/netlink.h>
#include <net/genetlink.h>
#include <uapi/linux/handshake.h>
#include <linux/err.h>
int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info);
int handshake_nl_done_doit(struct sk_buff *skb, struct genl_info *info);
enum {
HANDSHAKE_NLGRP_NONE,
HANDSHAKE_NLGRP_TLSHD,
};
extern struct genl_family handshake_nl_family;
#endif /* _LINUX_HANDSHAKE_GEN_H */
+576
View File
@@ -0,0 +1,576 @@
// 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, 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 | __GFP_NOWARN,
.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;
unsigned long fcount_before;
struct socket *sock;
struct file *filp;
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);
filp = sock_alloc_file(sock, O_NONBLOCK, NULL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filp);
KUNIT_ASSERT_NOT_NULL(test, sock->sk);
sock->file = filp;
fcount_before = file_count(filp);
err = handshake_req_submit(sock, req, GFP_KERNEL);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_EXPECT_EQ(test, file_count(filp), fcount_before + 1);
/* 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);
KUNIT_EXPECT_EQ(test, file_count(filp), fcount_before);
fput(filp);
}
static void handshake_req_submit_test5(struct kunit *test)
{
unsigned long fcount_before;
struct handshake_req *req;
struct handshake_net *hn;
struct socket *sock;
struct file *filp;
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);
filp = sock_alloc_file(sock, O_NONBLOCK, NULL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filp);
KUNIT_ASSERT_NOT_NULL(test, sock->sk);
sock->file = filp;
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;
fcount_before = file_count(filp);
/* Act */
err = handshake_req_submit(sock, req, GFP_KERNEL);
/* Assert */
KUNIT_EXPECT_EQ(test, err, -EAGAIN);
KUNIT_EXPECT_EQ(test, file_count(filp), fcount_before);
fput(filp);
hn->hn_pending = saved;
}
static void handshake_req_submit_test6(struct kunit *test)
{
struct handshake_req *req1, *req2;
unsigned long fcount_before;
struct socket *sock;
struct file *filp;
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);
filp = sock_alloc_file(sock, O_NONBLOCK, NULL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filp);
KUNIT_ASSERT_NOT_NULL(test, sock->sk);
sock->file = filp;
fcount_before = file_count(filp);
/* Act */
err = handshake_req_submit(sock, req1, GFP_KERNEL);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_EXPECT_EQ(test, file_count(filp), fcount_before + 1);
err = handshake_req_submit(sock, req2, GFP_KERNEL);
/* Assert */
KUNIT_EXPECT_EQ(test, err, -EBUSY);
KUNIT_EXPECT_EQ(test, file_count(filp), fcount_before + 1);
handshake_req_cancel(sock->sk);
KUNIT_EXPECT_EQ(test, file_count(filp), fcount_before);
fput(filp);
}
static void handshake_req_cancel_test1(struct kunit *test)
{
unsigned long fcount_before;
struct handshake_req *req;
struct socket *sock;
struct file *filp;
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);
filp = sock_alloc_file(sock, O_NONBLOCK, NULL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filp);
sock->file = filp;
fcount_before = file_count(filp);
err = handshake_req_submit(sock, req, GFP_KERNEL);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_EXPECT_EQ(test, file_count(filp), fcount_before + 1);
/* NB: handshake_req hasn't been accepted */
/* Act */
result = handshake_req_cancel(sock->sk);
/* Assert */
KUNIT_EXPECT_TRUE(test, result);
KUNIT_EXPECT_EQ(test, file_count(filp), fcount_before);
fput(filp);
}
static void handshake_req_cancel_test2(struct kunit *test)
{
unsigned long fcount_before;
struct handshake_req *req, *next;
struct handshake_net *hn;
struct socket *sock;
struct file *filp;
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);
filp = sock_alloc_file(sock, O_NONBLOCK, NULL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filp);
sock->file = filp;
fcount_before = file_count(filp);
err = handshake_req_submit(sock, req, GFP_KERNEL);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_EXPECT_EQ(test, file_count(filp), fcount_before + 1);
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);
/* Simulate FD_PREPARE() consuming the file reference handed
* off by handshake_req_next(); see handshake_nl_accept_doit().
*/
fput(filp);
/* Act */
result = handshake_req_cancel(sock->sk);
/* Assert */
KUNIT_EXPECT_TRUE(test, result);
KUNIT_EXPECT_EQ(test, file_count(filp), fcount_before);
fput(filp);
}
static void handshake_req_cancel_test3(struct kunit *test)
{
unsigned long fcount_before;
struct handshake_req *req, *next;
struct handshake_net *hn;
struct socket *sock;
struct file *filp;
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);
filp = sock_alloc_file(sock, O_NONBLOCK, NULL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filp);
sock->file = filp;
fcount_before = file_count(filp);
err = handshake_req_submit(sock, req, GFP_KERNEL);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_EXPECT_EQ(test, file_count(filp), fcount_before + 1);
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);
/* Simulate FD_PREPARE() consuming the file reference handed
* off by handshake_req_next(); see handshake_nl_accept_doit().
*/
fput(filp);
/* Pretend to complete this request */
handshake_complete(next, -ETIMEDOUT, NULL);
KUNIT_EXPECT_EQ(test, file_count(filp), fcount_before);
/* Act */
result = handshake_req_cancel(sock->sk);
/* Assert */
KUNIT_EXPECT_FALSE(test, result);
KUNIT_EXPECT_EQ(test, file_count(filp), fcount_before);
fput(filp);
}
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)
{
unsigned long fcount_before;
struct handshake_req *req;
struct socket *sock;
struct file *filp;
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);
filp = sock_alloc_file(sock, O_NONBLOCK, NULL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filp);
sock->file = filp;
fcount_before = file_count(filp);
err = handshake_req_submit(sock, req, GFP_KERNEL);
KUNIT_ASSERT_EQ(test, err, 0);
handshake_req_cancel(sock->sk);
KUNIT_EXPECT_EQ(test, file_count(filp), fcount_before);
/* Act */
/* Ensure the close/release/put process has run to
* completion before checking the result.
*/
__fput_sync(filp);
/* 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");
+95
View File
@@ -0,0 +1,95 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Generic netlink handshake service
*
* Author: Chuck Lever <chuck.lever@oracle.com>
*
* Copyright (c) 2023, Oracle and/or its affiliates.
*/
#ifndef _INTERNAL_HANDSHAKE_H
#define _INTERNAL_HANDSHAKE_H
/* Per-net namespace context */
struct handshake_net {
spinlock_t hn_lock; /* protects next 3 fields */
int hn_pending;
int hn_pending_max;
struct list_head hn_requests;
unsigned long hn_flags;
};
enum hn_flags_bits {
HANDSHAKE_F_NET_DRAINING,
};
struct file;
struct handshake_proto;
/* One handshake request */
struct handshake_req {
struct list_head hr_list;
struct rhash_head hr_rhash;
unsigned long hr_flags;
const struct handshake_proto *hr_proto;
struct file *hr_file;
struct sock *hr_sk;
void (*hr_odestruct)(struct sock *sk);
/* Always the last field */
char hr_priv[];
};
enum hr_flags_bits {
HANDSHAKE_F_REQ_COMPLETED,
HANDSHAKE_F_REQ_SESSION,
};
struct genl_info;
/* Invariants for all handshake requests for one transport layer
* security protocol
*/
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);
void (*hp_done)(struct handshake_req *req,
int status,
struct genl_info *info);
void (*hp_destroy)(struct handshake_req *req);
};
enum hp_flags_bits {
HANDSHAKE_F_PROTO_NOTIFY,
};
/* alert.c */
int tls_alert_send(struct socket *sock, u8 level, u8 description);
/* netlink.c */
int handshake_genl_notify(struct net *net, const struct handshake_proto *proto,
gfp_t flags);
struct nlmsghdr *handshake_genl_put(struct sk_buff *msg,
struct genl_info *info);
struct handshake_net *handshake_pernet(struct net *net);
/* request.c */
struct handshake_req *handshake_req_alloc(const struct handshake_proto *proto,
gfp_t flags);
int handshake_req_hash_init(void);
void handshake_req_hash_destroy(void);
void *handshake_req_private(struct handshake_req *req);
struct handshake_req *handshake_req_hash_lookup(struct sock *sk);
struct handshake_req *handshake_req_next(struct handshake_net *hn, int class);
int handshake_req_submit(struct socket *sock, struct handshake_req *req,
gfp_t flags);
void handshake_complete(struct handshake_req *req, int status,
struct genl_info *info);
bool handshake_req_cancel(struct sock *sk);
#endif /* _INTERNAL_HANDSHAKE_H */
+287
View File
@@ -0,0 +1,287 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Generic netlink handshake service
*
* Author: Chuck Lever <chuck.lever@oracle.com>
*
* Copyright (c) 2023, Oracle and/or its affiliates.
*/
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/mm.h>
#include <net/sock.h>
#include <net/genetlink.h>
#include <net/netns/generic.h>
#include <kunit/visibility.h>
#include <uapi/linux/handshake.h>
#include "handshake.h"
#include "genl.h"
#include <trace/events/handshake.h>
/**
* handshake_genl_notify - Notify handlers that a request is waiting
* @net: target network namespace
* @proto: handshake protocol
* @flags: memory allocation control flags
*
* Returns zero on success or a negative errno if notification failed.
*/
int handshake_genl_notify(struct net *net, const struct handshake_proto *proto,
gfp_t flags)
{
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;
msg = genlmsg_new(GENLMSG_DEFAULT_SIZE, flags);
if (!msg)
return -ENOMEM;
hdr = genlmsg_put(msg, 0, 0, &handshake_nl_family, 0,
HANDSHAKE_CMD_READY);
if (!hdr)
goto out_free;
if (nla_put_u32(msg, HANDSHAKE_A_ACCEPT_HANDLER_CLASS,
proto->hp_handler_class) < 0) {
genlmsg_cancel(msg, hdr);
goto out_free;
}
genlmsg_end(msg, hdr);
return genlmsg_multicast_netns(&handshake_nl_family, net, msg,
0, proto->hp_handler_class, flags);
out_free:
nlmsg_free(msg);
return -EMSGSIZE;
}
/**
* handshake_genl_put - Create a generic netlink message header
* @msg: buffer in which to create the header
* @info: generic netlink message context
*
* Returns a ready-to-use header, or NULL.
*/
struct nlmsghdr *handshake_genl_put(struct sk_buff *msg,
struct genl_info *info)
{
return genlmsg_put(msg, info->snd_portid, info->snd_seq,
&handshake_nl_family, 0, info->genlhdr->cmd);
}
EXPORT_SYMBOL(handshake_genl_put);
int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info)
{
struct net *net = sock_net(skb->sk);
struct handshake_net *hn = handshake_pernet(net);
struct handshake_req *req = NULL;
int class, err;
err = -EOPNOTSUPP;
if (!hn)
goto out_status;
err = -EINVAL;
if (GENL_REQ_ATTR_CHECK(info, HANDSHAKE_A_ACCEPT_HANDLER_CLASS))
goto out_status;
class = nla_get_u32(info->attrs[HANDSHAKE_A_ACCEPT_HANDLER_CLASS]);
err = -EAGAIN;
req = handshake_req_next(hn, class);
if (req) {
FD_PREPARE(fdf, O_CLOEXEC, req->hr_file);
if (fdf.err) {
fput(req->hr_file); /* drop ref from handshake_req_next() */
err = fdf.err;
goto out_complete;
}
err = req->hr_proto->hp_accept(req, info, fd_prepare_fd(fdf));
if (err)
goto out_complete; /* Automatic cleanup handles fput */
trace_handshake_cmd_accept(net, req, req->hr_sk, fd_prepare_fd(fdf));
fd_publish(fdf);
return 0;
}
out_complete:
if (req)
handshake_complete(req, -EIO, NULL);
out_status:
trace_handshake_cmd_accept_err(net, req, NULL, err);
return err;
}
int handshake_nl_done_doit(struct sk_buff *skb, struct genl_info *info)
{
struct net *net = sock_net(skb->sk);
struct handshake_req *req;
struct socket *sock;
int fd, status, err;
if (GENL_REQ_ATTR_CHECK(info, HANDSHAKE_A_DONE_SOCKFD))
return -EINVAL;
fd = nla_get_s32(info->attrs[HANDSHAKE_A_DONE_SOCKFD]);
sock = sockfd_lookup(fd, &err);
if (!sock)
return err;
req = handshake_req_hash_lookup(sock->sk);
if (!req) {
err = -EBUSY;
trace_handshake_cmd_done_err(net, req, sock->sk, err);
sockfd_put(sock);
return err;
}
trace_handshake_cmd_done(net, req, sock->sk, fd);
status = -EIO;
if (info->attrs[HANDSHAKE_A_DONE_STATUS])
status = -(int)nla_get_u32(info->attrs[HANDSHAKE_A_DONE_STATUS]);
handshake_complete(req, status, info);
sockfd_put(sock);
return 0;
}
static unsigned int handshake_net_id;
static int __net_init handshake_net_init(struct net *net)
{
struct handshake_net *hn = net_generic(net, handshake_net_id);
unsigned long tmp;
struct sysinfo si;
/*
* Arbitrary limit to prevent handshakes that do not make
* progress from clogging up the system. The cap scales up
* with the amount of physical memory on the system.
*/
si_meminfo(&si);
tmp = si.totalram / (25 * si.mem_unit);
hn->hn_pending_max = clamp(tmp, 3UL, 50UL);
spin_lock_init(&hn->hn_lock);
hn->hn_pending = 0;
hn->hn_flags = 0;
INIT_LIST_HEAD(&hn->hn_requests);
return 0;
}
static void __net_exit handshake_net_exit(struct net *net)
{
struct handshake_net *hn = net_generic(net, handshake_net_id);
struct handshake_req *req;
LIST_HEAD(requests);
/*
* Drain the net's pending list. Requests that have been
* accepted and are in progress will be destroyed when
* the socket is closed.
*/
spin_lock_bh(&hn->hn_lock);
set_bit(HANDSHAKE_F_NET_DRAINING, &hn->hn_flags);
list_splice_init(&hn->hn_requests, &requests);
list_for_each_entry(req, &requests, hr_list)
get_file(req->hr_file);
spin_unlock_bh(&hn->hn_lock);
while (!list_empty(&requests)) {
struct file *file;
req = list_first_entry(&requests, struct handshake_req, hr_list);
file = req->hr_file;
list_del_init(&req->hr_list);
handshake_complete(req, -ETIMEDOUT, NULL);
fput(file);
}
}
static struct pernet_operations handshake_genl_net_ops = {
.init = handshake_net_init,
.exit = handshake_net_exit,
.id = &handshake_net_id,
.size = sizeof(struct handshake_net),
};
/**
* handshake_pernet - Get the handshake private per-net structure
* @net: network namespace
*
* Returns a pointer to the net's private per-net structure for the
* handshake module, or NULL if handshake_init() failed.
*/
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)
{
int ret;
ret = handshake_req_hash_init();
if (ret) {
pr_warn("handshake: hash initialization failed (%d)\n", ret);
return ret;
}
ret = genl_register_family(&handshake_nl_family);
if (ret) {
pr_warn("handshake: netlink registration failed (%d)\n", ret);
handshake_req_hash_destroy();
return ret;
}
/*
* ORDER: register_pernet_subsys must be done last.
*
* If initialization does not make it past pernet_subsys
* registration, then handshake_net_id will remain 0. That
* shunts the handshake consumer API to return ENOTSUPP
* to prevent it from dereferencing something that hasn't
* been allocated.
*/
ret = register_pernet_subsys(&handshake_genl_net_ops);
if (ret) {
pr_warn("handshake: pernet registration failed (%d)\n", ret);
genl_unregister_family(&handshake_nl_family);
handshake_req_hash_destroy();
}
return ret;
}
static void __exit handshake_exit(void)
{
unregister_pernet_subsys(&handshake_genl_net_ops);
handshake_net_id = 0;
handshake_req_hash_destroy();
genl_unregister_family(&handshake_nl_family);
}
module_init(handshake_init);
module_exit(handshake_exit);
+386
View File
@@ -0,0 +1,386 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Handshake request lifetime events
*
* Author: Chuck Lever <chuck.lever@oracle.com>
*
* Copyright (c) 2023, Oracle and/or its affiliates.
*/
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/inet.h>
#include <linux/file.h>
#include <linux/rhashtable.h>
#include <net/sock.h>
#include <net/genetlink.h>
#include <net/netns/generic.h>
#include <kunit/visibility.h>
#include <uapi/linux/handshake.h>
#include "handshake.h"
#include <trace/events/handshake.h>
/*
* 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);
}
EXPORT_SYMBOL_IF_KUNIT(handshake_req_hash_lookup);
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_flex(*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.
* Returns %false when the net namespace is draining; the drain
* loop has taken ownership of the pending list.
*/
static bool remove_pending(struct handshake_net *hn, struct handshake_req *req)
{
bool ret = false;
spin_lock_bh(&hn->hn_lock);
if (!test_bit(HANDSHAKE_F_NET_DRAINING, &hn->hn_flags) &&
!list_empty(&req->hr_list)) {
__remove_pending_locked(hn, req);
ret = true;
}
spin_unlock_bh(&hn->hn_lock);
return ret;
}
/**
* handshake_req_next - Return the next queued handshake request
* @hn: per-net handshake state
* @class: handler class to match
*
* On a non-NULL return, the caller owns an extra reference
* on @req->hr_file. FD_PREPARE() consumes it on success; on
* the FD_PREPARE() failure path the caller must fput() it.
*
* Return: pointer to a removed handshake_req, or NULL.
*/
struct handshake_req *handshake_req_next(struct handshake_net *hn, int class)
{
struct handshake_req *req, *pos;
req = NULL;
spin_lock_bh(&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);
/* Hand off a file reference to the accept side under
* hn_lock. A concurrent handshake_req_cancel() can drop
* hr_file before accept reaches FD_PREPARE(); this extra
* reference keeps the file alive until FD_PREPARE() takes
* ownership.
*/
get_file(pos->hr_file);
req = pos;
break;
}
spin_unlock_bh(&hn->hn_lock);
return req;
}
EXPORT_SYMBOL_IF_KUNIT(handshake_req_next);
/**
* 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() guarantees that
* no completion callback will occur and that @req is no longer owned by
* the caller. If cancellation wins the completion race after the request
* has been published, final destruction is deferred until socket teardown.
*
* The caller must hold a reference on @sock->file for the duration
* of this call. Once the request is published to the accept side, a
* concurrent completion or cancellation may release the request's pin on
* @sock->file; the caller's reference is what keeps @sock->sk valid until
* handshake_req_submit() returns.
*/
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;
}
/*
* Pin sock->file for the lifetime of the request so the
* accept side does not race a consumer that releases the
* socket while a handshake is pending.
*/
req->hr_file = get_file(sock->file);
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_bh(&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_bh(&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;
}
trace_handshake_submit(net, req, req->hr_sk);
return 0;
out_unlock:
spin_unlock_bh(&hn->hn_lock);
out_err:
trace_handshake_submit_err(net, req, req->hr_sk, ret);
if (!test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) {
/* Restore original destructor so socket teardown still runs. */
req->hr_sk->sk_destruct = req->hr_odestruct;
fput(req->hr_file);
handshake_req_destroy(req);
}
return ret;
}
EXPORT_SYMBOL(handshake_req_submit);
void handshake_complete(struct handshake_req *req, 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)) {
struct file *file = req->hr_file;
trace_handshake_complete(net, req, sk, status);
req->hr_proto->hp_done(req, status, info);
fput(file);
}
}
EXPORT_SYMBOL_IF_KUNIT(handshake_complete);
/**
* 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 - mark cancelled */
if (test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) {
trace_handshake_cancel_busy(net, req, sk);
return false;
}
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);
fput(req->hr_file);
return true;
}
EXPORT_SYMBOL(handshake_req_cancel);
+457
View File
@@ -0,0 +1,457 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Establish a TLS session for a kernel socket consumer
* using the tlshd user space handler.
*
* Author: Chuck Lever <chuck.lever@oracle.com>
*
* Copyright (c) 2021-2023, Oracle and/or its affiliates.
*/
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/key.h>
#include <net/sock.h>
#include <net/handshake.h>
#include <net/genetlink.h>
#include <net/tls_prot.h>
#include <uapi/linux/keyctl.h>
#include <uapi/linux/handshake.h>
#include "handshake.h"
struct tls_handshake_req {
void (*th_consumer_done)(void *data, int status,
key_serial_t peerid);
void *th_consumer_data;
int th_type;
unsigned int th_timeout_ms;
int th_auth_mode;
const char *th_peername;
key_serial_t th_keyring;
key_serial_t th_certificate;
key_serial_t th_privkey;
unsigned int th_num_peerids;
key_serial_t th_peerid[5];
};
static struct tls_handshake_req *
tls_handshake_req_init(struct handshake_req *req,
const struct tls_handshake_args *args)
{
struct tls_handshake_req *treq = handshake_req_private(req);
treq->th_timeout_ms = args->ta_timeout_ms;
treq->th_consumer_done = args->ta_done;
treq->th_consumer_data = args->ta_data;
treq->th_peername = args->ta_peername;
treq->th_keyring = args->ta_keyring;
treq->th_num_peerids = 0;
treq->th_certificate = TLS_NO_CERT;
treq->th_privkey = TLS_NO_PRIVKEY;
return treq;
}
static void tls_handshake_remote_peerids(struct tls_handshake_req *treq,
struct genl_info *info)
{
struct nlattr *head = nlmsg_attrdata(info->nlhdr, GENL_HDRLEN);
int rem, len = nlmsg_attrlen(info->nlhdr, GENL_HDRLEN);
struct nlattr *nla;
unsigned int i;
i = 0;
nla_for_each_attr(nla, head, len, rem) {
if (nla_type(nla) == HANDSHAKE_A_DONE_REMOTE_AUTH)
i++;
}
if (!i)
return;
treq->th_num_peerids = min_t(unsigned int, i,
ARRAY_SIZE(treq->th_peerid));
i = 0;
nla_for_each_attr(nla, head, len, rem) {
if (nla_type(nla) == HANDSHAKE_A_DONE_REMOTE_AUTH)
treq->th_peerid[i++] = nla_get_u32(nla);
if (i >= treq->th_num_peerids)
break;
}
}
/**
* tls_handshake_done - callback to handle a CMD_DONE request
* @req: socket on which the handshake was performed
* @status: session status code
* @info: full results of session establishment
*
*/
static void tls_handshake_done(struct handshake_req *req,
int status, struct genl_info *info)
{
struct tls_handshake_req *treq = handshake_req_private(req);
treq->th_peerid[0] = TLS_NO_PEERID;
if (info)
tls_handshake_remote_peerids(treq, info);
if (!status)
set_bit(HANDSHAKE_F_REQ_SESSION, &req->hr_flags);
treq->th_consumer_done(treq->th_consumer_data, status,
treq->th_peerid[0]);
}
#if IS_ENABLED(CONFIG_KEYS)
static int tls_handshake_private_keyring(struct tls_handshake_req *treq)
{
key_ref_t process_keyring_ref, keyring_ref;
int ret;
if (treq->th_keyring == TLS_NO_KEYRING)
return 0;
process_keyring_ref = lookup_user_key(KEY_SPEC_PROCESS_KEYRING,
KEY_LOOKUP_CREATE,
KEY_NEED_WRITE);
if (IS_ERR(process_keyring_ref)) {
ret = PTR_ERR(process_keyring_ref);
goto out;
}
keyring_ref = lookup_user_key(treq->th_keyring, KEY_LOOKUP_CREATE,
KEY_NEED_LINK);
if (IS_ERR(keyring_ref)) {
ret = PTR_ERR(keyring_ref);
goto out_put_key;
}
ret = key_link(key_ref_to_ptr(process_keyring_ref),
key_ref_to_ptr(keyring_ref));
key_ref_put(keyring_ref);
out_put_key:
key_ref_put(process_keyring_ref);
out:
return ret;
}
#else
static int tls_handshake_private_keyring(struct tls_handshake_req *treq)
{
return 0;
}
#endif
static int tls_handshake_put_peer_identity(struct sk_buff *msg,
struct tls_handshake_req *treq)
{
unsigned int i;
for (i = 0; i < treq->th_num_peerids; i++)
if (nla_put_u32(msg, HANDSHAKE_A_ACCEPT_PEER_IDENTITY,
treq->th_peerid[i]) < 0)
return -EMSGSIZE;
return 0;
}
static int tls_handshake_put_certificate(struct sk_buff *msg,
struct tls_handshake_req *treq)
{
struct nlattr *entry_attr;
if (treq->th_certificate == TLS_NO_CERT &&
treq->th_privkey == TLS_NO_PRIVKEY)
return 0;
entry_attr = nla_nest_start(msg, HANDSHAKE_A_ACCEPT_CERTIFICATE);
if (!entry_attr)
return -EMSGSIZE;
if (nla_put_s32(msg, HANDSHAKE_A_X509_CERT,
treq->th_certificate) ||
nla_put_s32(msg, HANDSHAKE_A_X509_PRIVKEY,
treq->th_privkey)) {
nla_nest_cancel(msg, entry_attr);
return -EMSGSIZE;
}
nla_nest_end(msg, entry_attr);
return 0;
}
/**
* tls_handshake_accept - callback to construct a CMD_ACCEPT response
* @req: handshake parameters to return
* @info: generic netlink message context
* @fd: file descriptor to be returned
*
* Returns zero on success, or a negative errno on failure.
*/
static int tls_handshake_accept(struct handshake_req *req,
struct genl_info *info, int fd)
{
struct tls_handshake_req *treq = handshake_req_private(req);
struct nlmsghdr *hdr;
struct sk_buff *msg;
int ret;
ret = tls_handshake_private_keyring(treq);
if (ret < 0)
goto out;
ret = -ENOMEM;
msg = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!msg)
goto out;
hdr = handshake_genl_put(msg, info);
if (!hdr)
goto out_cancel;
ret = nla_put_s32(msg, HANDSHAKE_A_ACCEPT_SOCKFD, fd);
if (ret < 0)
goto out_cancel;
ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_MESSAGE_TYPE, treq->th_type);
if (ret < 0)
goto out_cancel;
if (treq->th_peername) {
ret = nla_put_string(msg, HANDSHAKE_A_ACCEPT_PEERNAME,
treq->th_peername);
if (ret < 0)
goto out_cancel;
}
if (treq->th_timeout_ms) {
ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_TIMEOUT, treq->th_timeout_ms);
if (ret < 0)
goto out_cancel;
}
if (treq->th_keyring) {
ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_KEYRING,
treq->th_keyring);
if (ret < 0)
goto out_cancel;
}
ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_AUTH_MODE,
treq->th_auth_mode);
if (ret < 0)
goto out_cancel;
switch (treq->th_auth_mode) {
case HANDSHAKE_AUTH_PSK:
ret = tls_handshake_put_peer_identity(msg, treq);
if (ret < 0)
goto out_cancel;
break;
case HANDSHAKE_AUTH_X509:
ret = tls_handshake_put_certificate(msg, treq);
if (ret < 0)
goto out_cancel;
break;
}
genlmsg_end(msg, hdr);
return genlmsg_reply(msg, info);
out_cancel:
genlmsg_cancel(msg, hdr);
nlmsg_free(msg);
out:
return ret;
}
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,
};
/**
* tls_client_hello_anon - request an anonymous TLS handshake on a socket
* @args: socket and handshake parameters for this request
* @flags: memory allocation control flags
*
* Return values:
* %0: Handshake request enqueue; ->done will be called when complete
* %-ESRCH: No user agent is available
* %-ENOMEM: Memory allocation failed
*/
int tls_client_hello_anon(const struct tls_handshake_args *args, gfp_t flags)
{
struct tls_handshake_req *treq;
struct handshake_req *req;
req = handshake_req_alloc(&tls_handshake_proto, flags);
if (!req)
return -ENOMEM;
treq = tls_handshake_req_init(req, args);
treq->th_type = HANDSHAKE_MSG_TYPE_CLIENTHELLO;
treq->th_auth_mode = HANDSHAKE_AUTH_UNAUTH;
return handshake_req_submit(args->ta_sock, req, flags);
}
EXPORT_SYMBOL(tls_client_hello_anon);
/**
* tls_client_hello_x509 - request an x.509-based TLS handshake on a socket
* @args: socket and handshake parameters for this request
* @flags: memory allocation control flags
*
* Return values:
* %0: Handshake request enqueue; ->done will be called when complete
* %-ESRCH: No user agent is available
* %-ENOMEM: Memory allocation failed
*/
int tls_client_hello_x509(const struct tls_handshake_args *args, gfp_t flags)
{
struct tls_handshake_req *treq;
struct handshake_req *req;
req = handshake_req_alloc(&tls_handshake_proto, flags);
if (!req)
return -ENOMEM;
treq = tls_handshake_req_init(req, args);
treq->th_type = HANDSHAKE_MSG_TYPE_CLIENTHELLO;
treq->th_auth_mode = HANDSHAKE_AUTH_X509;
treq->th_certificate = args->ta_my_cert;
treq->th_privkey = args->ta_my_privkey;
return handshake_req_submit(args->ta_sock, req, flags);
}
EXPORT_SYMBOL(tls_client_hello_x509);
/**
* tls_client_hello_psk - request a PSK-based TLS handshake on a socket
* @args: socket and handshake parameters for this request
* @flags: memory allocation control flags
*
* Return values:
* %0: Handshake request enqueue; ->done will be called when complete
* %-EINVAL: Wrong number of local peer IDs
* %-ESRCH: No user agent is available
* %-ENOMEM: Memory allocation failed
*/
int tls_client_hello_psk(const struct tls_handshake_args *args, gfp_t flags)
{
struct tls_handshake_req *treq;
struct handshake_req *req;
unsigned int i;
if (!args->ta_num_peerids ||
args->ta_num_peerids > ARRAY_SIZE(treq->th_peerid))
return -EINVAL;
req = handshake_req_alloc(&tls_handshake_proto, flags);
if (!req)
return -ENOMEM;
treq = tls_handshake_req_init(req, args);
treq->th_type = HANDSHAKE_MSG_TYPE_CLIENTHELLO;
treq->th_auth_mode = HANDSHAKE_AUTH_PSK;
treq->th_num_peerids = args->ta_num_peerids;
for (i = 0; i < args->ta_num_peerids; i++)
treq->th_peerid[i] = args->ta_my_peerids[i];
return handshake_req_submit(args->ta_sock, req, flags);
}
EXPORT_SYMBOL(tls_client_hello_psk);
/**
* tls_server_hello_x509 - request a server TLS handshake on a socket
* @args: socket and handshake parameters for this request
* @flags: memory allocation control flags
*
* Return values:
* %0: Handshake request enqueue; ->done will be called when complete
* %-ESRCH: No user agent is available
* %-ENOMEM: Memory allocation failed
*/
int tls_server_hello_x509(const struct tls_handshake_args *args, gfp_t flags)
{
struct tls_handshake_req *treq;
struct handshake_req *req;
req = handshake_req_alloc(&tls_handshake_proto, flags);
if (!req)
return -ENOMEM;
treq = tls_handshake_req_init(req, args);
treq->th_type = HANDSHAKE_MSG_TYPE_SERVERHELLO;
treq->th_auth_mode = HANDSHAKE_AUTH_X509;
treq->th_certificate = args->ta_my_cert;
treq->th_privkey = args->ta_my_privkey;
return handshake_req_submit(args->ta_sock, req, flags);
}
EXPORT_SYMBOL(tls_server_hello_x509);
/**
* tls_server_hello_psk - request a server TLS handshake on a socket
* @args: socket and handshake parameters for this request
* @flags: memory allocation control flags
*
* Return values:
* %0: Handshake request enqueue; ->done will be called when complete
* %-ESRCH: No user agent is available
* %-ENOMEM: Memory allocation failed
*/
int tls_server_hello_psk(const struct tls_handshake_args *args, gfp_t flags)
{
struct tls_handshake_req *treq;
struct handshake_req *req;
req = handshake_req_alloc(&tls_handshake_proto, flags);
if (!req)
return -ENOMEM;
treq = tls_handshake_req_init(req, args);
treq->th_type = HANDSHAKE_MSG_TYPE_SERVERHELLO;
treq->th_auth_mode = HANDSHAKE_AUTH_PSK;
treq->th_num_peerids = 1;
treq->th_peerid[0] = args->ta_my_peerids[0];
return handshake_req_submit(args->ta_sock, req, flags);
}
EXPORT_SYMBOL(tls_server_hello_psk);
/**
* tls_handshake_cancel - cancel a pending 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.
*
* Context: May be called from process or softirq context.
*
* Return values:
* %true - Uncompleted handshake request was canceled
* %false - Handshake request already completed or not found
*/
bool tls_handshake_cancel(struct sock *sk)
{
return handshake_req_cancel(sk);
}
EXPORT_SYMBOL(tls_handshake_cancel);
/**
* tls_handshake_close - send a Closure alert
* @sock: an open socket
*
*/
void tls_handshake_close(struct socket *sock)
{
struct handshake_req *req;
req = handshake_req_hash_lookup(sock->sk);
if (!req)
return;
if (!test_and_clear_bit(HANDSHAKE_F_REQ_SESSION, &req->hr_flags))
return;
tls_alert_send(sock, TLS_ALERT_LEVEL_WARNING,
TLS_ALERT_DESC_CLOSE_NOTIFY);
}
EXPORT_SYMBOL(tls_handshake_close);
+22
View File
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Trace points for transport security layer handshakes.
*
* Author: Chuck Lever <chuck.lever@oracle.com>
*
* Copyright (c) 2023, Oracle and/or its affiliates.
*/
#include <linux/types.h>
#include <linux/ipv6.h>
#include <net/sock.h>
#include <net/inet_sock.h>
#include <net/netlink.h>
#include <net/genetlink.h>
#include "handshake.h"
#define CREATE_TRACE_POINTS
#include <trace/events/handshake.h>