// 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);
Messung V0.5 in Prozent C=97 H=97 G=96
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland