// 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 #include #include #include #include #include #include #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);