mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

Provide a way for the application (e.g. the afs filesystem) to store private data on the rxrpc_peer structs for later retrieval via the call object. This will allow afs to store a pointer to the afs_server object on the rxrpc_peer struct, thereby obviating the need for afs to keep lookup tables by which it can associate an incoming call with server that transmitted it. Signed-off-by: David Howells <dhowells@redhat.com> cc: Marc Dionne <marc.dionne@auristor.com> cc: Simon Horman <horms@kernel.org> cc: linux-afs@lists.infradead.org Link: https://patch.msgid.link/20250411095303.2316168-11-dhowells@redhat.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
379 lines
9.1 KiB
C
379 lines
9.1 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/* Out of band message handling (e.g. challenge-response)
|
|
*
|
|
* Copyright (C) 2025 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/net.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/export.h>
|
|
#include <linux/sched/signal.h>
|
|
#include <net/sock.h>
|
|
#include <net/af_rxrpc.h>
|
|
#include "ar-internal.h"
|
|
|
|
enum rxrpc_oob_command {
|
|
RXRPC_OOB_CMD_UNSET,
|
|
RXRPC_OOB_CMD_RESPOND,
|
|
} __mode(byte);
|
|
|
|
struct rxrpc_oob_params {
|
|
u64 oob_id; /* ID number of message if reply */
|
|
s32 abort_code;
|
|
enum rxrpc_oob_command command;
|
|
bool have_oob_id:1;
|
|
};
|
|
|
|
/*
|
|
* Post an out-of-band message for attention by the socket or kernel service
|
|
* associated with a reference call.
|
|
*/
|
|
void rxrpc_notify_socket_oob(struct rxrpc_call *call, struct sk_buff *skb)
|
|
{
|
|
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
|
|
struct rxrpc_sock *rx;
|
|
struct sock *sk;
|
|
|
|
rcu_read_lock();
|
|
|
|
rx = rcu_dereference(call->socket);
|
|
if (rx) {
|
|
sk = &rx->sk;
|
|
spin_lock_irq(&rx->recvmsg_lock);
|
|
|
|
if (sk->sk_state < RXRPC_CLOSE) {
|
|
skb->skb_mstamp_ns = rx->oob_id_counter++;
|
|
rxrpc_get_skb(skb, rxrpc_skb_get_post_oob);
|
|
skb_queue_tail(&rx->recvmsg_oobq, skb);
|
|
|
|
trace_rxrpc_notify_socket(call->debug_id, sp->hdr.serial);
|
|
if (rx->app_ops)
|
|
rx->app_ops->notify_oob(sk, skb);
|
|
}
|
|
|
|
spin_unlock_irq(&rx->recvmsg_lock);
|
|
if (!rx->app_ops && !sock_flag(sk, SOCK_DEAD))
|
|
sk->sk_data_ready(sk);
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
/*
|
|
* Locate the OOB message to respond to by its ID.
|
|
*/
|
|
static struct sk_buff *rxrpc_find_pending_oob(struct rxrpc_sock *rx, u64 oob_id)
|
|
{
|
|
struct rb_node *p;
|
|
struct sk_buff *skb;
|
|
|
|
p = rx->pending_oobq.rb_node;
|
|
while (p) {
|
|
skb = rb_entry(p, struct sk_buff, rbnode);
|
|
|
|
if (oob_id < skb->skb_mstamp_ns)
|
|
p = p->rb_left;
|
|
else if (oob_id > skb->skb_mstamp_ns)
|
|
p = p->rb_right;
|
|
else
|
|
return skb;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Add an OOB message into the pending-response set. We always assign the next
|
|
* value from a 64-bit counter to the oob_id, so just assume we're always going
|
|
* to be on the right-hand edge of the tree and that the counter won't wrap.
|
|
* The tree is also given a ref to the message.
|
|
*/
|
|
void rxrpc_add_pending_oob(struct rxrpc_sock *rx, struct sk_buff *skb)
|
|
{
|
|
struct rb_node **pp = &rx->pending_oobq.rb_node, *p = NULL;
|
|
|
|
while (*pp) {
|
|
p = *pp;
|
|
pp = &(*pp)->rb_right;
|
|
}
|
|
|
|
rb_link_node(&skb->rbnode, p, pp);
|
|
rb_insert_color(&skb->rbnode, &rx->pending_oobq);
|
|
}
|
|
|
|
/*
|
|
* Extract control messages from the sendmsg() control buffer.
|
|
*/
|
|
static int rxrpc_sendmsg_oob_cmsg(struct msghdr *msg, struct rxrpc_oob_params *p)
|
|
{
|
|
struct cmsghdr *cmsg;
|
|
int len;
|
|
|
|
if (msg->msg_controllen == 0)
|
|
return -EINVAL;
|
|
|
|
for_each_cmsghdr(cmsg, msg) {
|
|
if (!CMSG_OK(msg, cmsg))
|
|
return -EINVAL;
|
|
|
|
len = cmsg->cmsg_len - sizeof(struct cmsghdr);
|
|
_debug("CMSG %d, %d, %d",
|
|
cmsg->cmsg_level, cmsg->cmsg_type, len);
|
|
|
|
if (cmsg->cmsg_level != SOL_RXRPC)
|
|
continue;
|
|
|
|
switch (cmsg->cmsg_type) {
|
|
case RXRPC_OOB_ID:
|
|
if (len != sizeof(p->oob_id) || p->have_oob_id)
|
|
return -EINVAL;
|
|
memcpy(&p->oob_id, CMSG_DATA(cmsg), sizeof(p->oob_id));
|
|
p->have_oob_id = true;
|
|
break;
|
|
case RXRPC_RESPOND:
|
|
if (p->command != RXRPC_OOB_CMD_UNSET)
|
|
return -EINVAL;
|
|
p->command = RXRPC_OOB_CMD_RESPOND;
|
|
break;
|
|
case RXRPC_ABORT:
|
|
if (len != sizeof(p->abort_code) || p->abort_code)
|
|
return -EINVAL;
|
|
memcpy(&p->abort_code, CMSG_DATA(cmsg), sizeof(p->abort_code));
|
|
if (p->abort_code == 0)
|
|
return -EINVAL;
|
|
break;
|
|
case RXRPC_RESP_RXGK_APPDATA:
|
|
if (p->command != RXRPC_OOB_CMD_RESPOND)
|
|
return -EINVAL;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
switch (p->command) {
|
|
case RXRPC_OOB_CMD_RESPOND:
|
|
if (!p->have_oob_id)
|
|
return -EBADSLT;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Allow userspace to respond to an OOB using sendmsg().
|
|
*/
|
|
static int rxrpc_respond_to_oob(struct rxrpc_sock *rx,
|
|
struct rxrpc_oob_params *p,
|
|
struct msghdr *msg)
|
|
{
|
|
struct rxrpc_connection *conn;
|
|
struct rxrpc_skb_priv *sp;
|
|
struct sk_buff *skb;
|
|
int ret;
|
|
|
|
skb = rxrpc_find_pending_oob(rx, p->oob_id);
|
|
if (skb)
|
|
rb_erase(&skb->rbnode, &rx->pending_oobq);
|
|
release_sock(&rx->sk);
|
|
if (!skb)
|
|
return -EBADSLT;
|
|
|
|
sp = rxrpc_skb(skb);
|
|
|
|
switch (p->command) {
|
|
case RXRPC_OOB_CMD_RESPOND:
|
|
ret = -EPROTO;
|
|
if (skb->mark != RXRPC_OOB_CHALLENGE)
|
|
break;
|
|
conn = sp->chall.conn;
|
|
ret = -EOPNOTSUPP;
|
|
if (!conn->security->sendmsg_respond_to_challenge)
|
|
break;
|
|
if (p->abort_code) {
|
|
rxrpc_abort_conn(conn, NULL, p->abort_code, -ECONNABORTED,
|
|
rxrpc_abort_response_sendmsg);
|
|
ret = 0;
|
|
} else {
|
|
ret = conn->security->sendmsg_respond_to_challenge(skb, msg);
|
|
}
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
rxrpc_free_skb(skb, rxrpc_skb_put_oob);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Send an out-of-band message or respond to a received out-of-band message.
|
|
* - caller gives us the socket lock
|
|
* - the socket may be either a client socket or a server socket
|
|
*/
|
|
int rxrpc_sendmsg_oob(struct rxrpc_sock *rx, struct msghdr *msg, size_t len)
|
|
{
|
|
struct rxrpc_oob_params p = {};
|
|
int ret;
|
|
|
|
_enter("");
|
|
|
|
ret = rxrpc_sendmsg_oob_cmsg(msg, &p);
|
|
if (ret < 0)
|
|
goto error_release_sock;
|
|
|
|
if (p.have_oob_id)
|
|
return rxrpc_respond_to_oob(rx, &p, msg);
|
|
|
|
release_sock(&rx->sk);
|
|
|
|
switch (p.command) {
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
_leave(" = %d", ret);
|
|
return ret;
|
|
|
|
error_release_sock:
|
|
release_sock(&rx->sk);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* rxrpc_kernel_query_oob - Query the parameters of an out-of-band message
|
|
* @oob: The message to query
|
|
* @_peer: Where to return the peer record
|
|
* @_peer_appdata: The application data attached to a peer record
|
|
*
|
|
* Extract useful parameters from an out-of-band message. The source peer
|
|
* parameters are returned through the argument list and the message type is
|
|
* returned.
|
|
*
|
|
* Return:
|
|
* * %RXRPC_OOB_CHALLENGE - Challenge wanting a response.
|
|
*/
|
|
enum rxrpc_oob_type rxrpc_kernel_query_oob(struct sk_buff *oob,
|
|
struct rxrpc_peer **_peer,
|
|
unsigned long *_peer_appdata)
|
|
{
|
|
struct rxrpc_skb_priv *sp = rxrpc_skb(oob);
|
|
enum rxrpc_oob_type type = oob->mark;
|
|
|
|
switch (type) {
|
|
case RXRPC_OOB_CHALLENGE:
|
|
*_peer = sp->chall.conn->peer;
|
|
*_peer_appdata = sp->chall.conn->peer->app_data;
|
|
break;
|
|
default:
|
|
WARN_ON_ONCE(1);
|
|
*_peer = NULL;
|
|
*_peer_appdata = 0;
|
|
break;
|
|
}
|
|
|
|
return type;
|
|
}
|
|
EXPORT_SYMBOL(rxrpc_kernel_query_oob);
|
|
|
|
/**
|
|
* rxrpc_kernel_dequeue_oob - Dequeue and return the front OOB message
|
|
* @sock: The socket to query
|
|
* @_type: Where to return the message type
|
|
*
|
|
* Dequeue the front OOB message, if there is one, and return it and
|
|
* its type.
|
|
*
|
|
* Return: The sk_buff representing the OOB message or %NULL if the queue was
|
|
* empty.
|
|
*/
|
|
struct sk_buff *rxrpc_kernel_dequeue_oob(struct socket *sock,
|
|
enum rxrpc_oob_type *_type)
|
|
{
|
|
struct rxrpc_sock *rx = rxrpc_sk(sock->sk);
|
|
struct sk_buff *oob;
|
|
|
|
oob = skb_dequeue(&rx->recvmsg_oobq);
|
|
if (oob)
|
|
*_type = oob->mark;
|
|
return oob;
|
|
}
|
|
EXPORT_SYMBOL(rxrpc_kernel_dequeue_oob);
|
|
|
|
/**
|
|
* rxrpc_kernel_free_oob - Free an out-of-band message
|
|
* @oob: The OOB message to free
|
|
*
|
|
* Free an OOB message along with any resources it holds.
|
|
*/
|
|
void rxrpc_kernel_free_oob(struct sk_buff *oob)
|
|
{
|
|
struct rxrpc_skb_priv *sp = rxrpc_skb(oob);
|
|
|
|
switch (oob->mark) {
|
|
case RXRPC_OOB_CHALLENGE:
|
|
rxrpc_put_connection(sp->chall.conn, rxrpc_conn_put_oob);
|
|
break;
|
|
}
|
|
|
|
rxrpc_free_skb(oob, rxrpc_skb_put_purge_oob);
|
|
}
|
|
EXPORT_SYMBOL(rxrpc_kernel_free_oob);
|
|
|
|
/**
|
|
* rxrpc_kernel_query_challenge - Query the parameters of a challenge
|
|
* @challenge: The challenge to query
|
|
* @_peer: Where to return the peer record
|
|
* @_peer_appdata: The application data attached to a peer record
|
|
* @_service_id: Where to return the connection service ID
|
|
* @_security_index: Where to return the connection security index
|
|
*
|
|
* Extract useful parameters from a CHALLENGE message.
|
|
*/
|
|
void rxrpc_kernel_query_challenge(struct sk_buff *challenge,
|
|
struct rxrpc_peer **_peer,
|
|
unsigned long *_peer_appdata,
|
|
u16 *_service_id, u8 *_security_index)
|
|
{
|
|
struct rxrpc_skb_priv *sp = rxrpc_skb(challenge);
|
|
|
|
*_peer = sp->chall.conn->peer;
|
|
*_peer_appdata = sp->chall.conn->peer->app_data;
|
|
*_service_id = sp->hdr.serviceId;
|
|
*_security_index = sp->hdr.securityIndex;
|
|
}
|
|
EXPORT_SYMBOL(rxrpc_kernel_query_challenge);
|
|
|
|
/**
|
|
* rxrpc_kernel_reject_challenge - Allow a kernel service to reject a challenge
|
|
* @challenge: The challenge to be rejected
|
|
* @abort_code: The abort code to stick into the ABORT packet
|
|
* @error: Local error value
|
|
* @why: Indication as to why.
|
|
*
|
|
* Allow a kernel service to reject a challenge by aborting the connection if
|
|
* it's still in an abortable state. The error is returned so this function
|
|
* can be used with a return statement.
|
|
*
|
|
* Return: The %error parameter.
|
|
*/
|
|
int rxrpc_kernel_reject_challenge(struct sk_buff *challenge, u32 abort_code,
|
|
int error, enum rxrpc_abort_reason why)
|
|
{
|
|
struct rxrpc_skb_priv *sp = rxrpc_skb(challenge);
|
|
|
|
_enter("{%x},%d,%d,%u", sp->hdr.serial, abort_code, error, why);
|
|
|
|
rxrpc_abort_conn(sp->chall.conn, NULL, abort_code, error, why);
|
|
return error;
|
|
}
|
|
EXPORT_SYMBOL(rxrpc_kernel_reject_challenge);
|