| // 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. | |
| */ | |
| bool 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; | |
| bool queued = false; | |
| 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); | |
| queued = true; | |
| 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 (queued && !rx->app_ops && !sock_flag(sk, SOCK_DEAD)) | |
| sk->sk_data_ready(sk); | |
| } | |
| rcu_read_unlock(); | |
| return queued; | |
| } | |
| /* | |
| * 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; | |
| } | |
| switch (skb->mark) { | |
| case RXRPC_OOB_CHALLENGE: | |
| rxrpc_put_connection(sp->chall.conn, rxrpc_conn_put_oob); | |
| 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); |