| // SPDX-License-Identifier: GPL-2.0-or-later | 
 | /* Service connection management | 
 |  * | 
 |  * Copyright (C) 2016 Red Hat, Inc. All Rights Reserved. | 
 |  * Written by David Howells (dhowells@redhat.com) | 
 |  */ | 
 |  | 
 | #include <linux/slab.h> | 
 | #include "ar-internal.h" | 
 |  | 
 | /* | 
 |  * Find a service connection under RCU conditions. | 
 |  * | 
 |  * We could use a hash table, but that is subject to bucket stuffing by an | 
 |  * attacker as the client gets to pick the epoch and cid values and would know | 
 |  * the hash function.  So, instead, we use a hash table for the peer and from | 
 |  * that an rbtree to find the service connection.  Under ordinary circumstances | 
 |  * it might be slower than a large hash table, but it is at least limited in | 
 |  * depth. | 
 |  */ | 
 | struct rxrpc_connection *rxrpc_find_service_conn_rcu(struct rxrpc_peer *peer, | 
 | 						     struct sk_buff *skb) | 
 | { | 
 | 	struct rxrpc_connection *conn = NULL; | 
 | 	struct rxrpc_conn_proto k; | 
 | 	struct rxrpc_skb_priv *sp = rxrpc_skb(skb); | 
 | 	struct rb_node *p; | 
 | 	unsigned int seq = 1; | 
 |  | 
 | 	k.epoch	= sp->hdr.epoch; | 
 | 	k.cid	= sp->hdr.cid & RXRPC_CIDMASK; | 
 |  | 
 | 	do { | 
 | 		/* Unfortunately, rbtree walking doesn't give reliable results | 
 | 		 * under just the RCU read lock, so we have to check for | 
 | 		 * changes. | 
 | 		 */ | 
 | 		seq++; /* 2 on the 1st/lockless path, otherwise odd */ | 
 | 		read_seqbegin_or_lock(&peer->service_conn_lock, &seq); | 
 |  | 
 | 		p = rcu_dereference_raw(peer->service_conns.rb_node); | 
 | 		while (p) { | 
 | 			conn = rb_entry(p, struct rxrpc_connection, service_node); | 
 |  | 
 | 			if (conn->proto.index_key < k.index_key) | 
 | 				p = rcu_dereference_raw(p->rb_left); | 
 | 			else if (conn->proto.index_key > k.index_key) | 
 | 				p = rcu_dereference_raw(p->rb_right); | 
 | 			else | 
 | 				break; | 
 | 			conn = NULL; | 
 | 		} | 
 | 	} while (need_seqretry(&peer->service_conn_lock, seq)); | 
 |  | 
 | 	done_seqretry(&peer->service_conn_lock, seq); | 
 | 	_leave(" = %d", conn ? conn->debug_id : -1); | 
 | 	return conn; | 
 | } | 
 |  | 
 | /* | 
 |  * Insert a service connection into a peer's tree, thereby making it a target | 
 |  * for incoming packets. | 
 |  */ | 
 | static void rxrpc_publish_service_conn(struct rxrpc_peer *peer, | 
 | 				       struct rxrpc_connection *conn) | 
 | { | 
 | 	struct rxrpc_connection *cursor = NULL; | 
 | 	struct rxrpc_conn_proto k = conn->proto; | 
 | 	struct rb_node **pp, *parent; | 
 |  | 
 | 	write_seqlock(&peer->service_conn_lock); | 
 |  | 
 | 	pp = &peer->service_conns.rb_node; | 
 | 	parent = NULL; | 
 | 	while (*pp) { | 
 | 		parent = *pp; | 
 | 		cursor = rb_entry(parent, | 
 | 				  struct rxrpc_connection, service_node); | 
 |  | 
 | 		if (cursor->proto.index_key < k.index_key) | 
 | 			pp = &(*pp)->rb_left; | 
 | 		else if (cursor->proto.index_key > k.index_key) | 
 | 			pp = &(*pp)->rb_right; | 
 | 		else | 
 | 			goto found_extant_conn; | 
 | 	} | 
 |  | 
 | 	rb_link_node_rcu(&conn->service_node, parent, pp); | 
 | 	rb_insert_color(&conn->service_node, &peer->service_conns); | 
 | conn_published: | 
 | 	set_bit(RXRPC_CONN_IN_SERVICE_CONNS, &conn->flags); | 
 | 	write_sequnlock(&peer->service_conn_lock); | 
 | 	_leave(" = %d [new]", conn->debug_id); | 
 | 	return; | 
 |  | 
 | found_extant_conn: | 
 | 	if (refcount_read(&cursor->ref) == 0) | 
 | 		goto replace_old_connection; | 
 | 	write_sequnlock(&peer->service_conn_lock); | 
 | 	/* We should not be able to get here.  rxrpc_incoming_connection() is | 
 | 	 * called in a non-reentrant context, so there can't be a race to | 
 | 	 * insert a new connection. | 
 | 	 */ | 
 | 	BUG(); | 
 |  | 
 | replace_old_connection: | 
 | 	/* The old connection is from an outdated epoch. */ | 
 | 	_debug("replace conn"); | 
 | 	rb_replace_node_rcu(&cursor->service_node, | 
 | 			    &conn->service_node, | 
 | 			    &peer->service_conns); | 
 | 	clear_bit(RXRPC_CONN_IN_SERVICE_CONNS, &cursor->flags); | 
 | 	goto conn_published; | 
 | } | 
 |  | 
 | /* | 
 |  * Preallocate a service connection.  The connection is placed on the proc and | 
 |  * reap lists so that we don't have to get the lock from BH context. | 
 |  */ | 
 | struct rxrpc_connection *rxrpc_prealloc_service_connection(struct rxrpc_net *rxnet, | 
 | 							   gfp_t gfp) | 
 | { | 
 | 	struct rxrpc_connection *conn = rxrpc_alloc_connection(rxnet, gfp); | 
 |  | 
 | 	if (conn) { | 
 | 		/* We maintain an extra ref on the connection whilst it is on | 
 | 		 * the rxrpc_connections list. | 
 | 		 */ | 
 | 		conn->state = RXRPC_CONN_SERVICE_PREALLOC; | 
 | 		refcount_set(&conn->ref, 2); | 
 |  | 
 | 		atomic_inc(&rxnet->nr_conns); | 
 | 		write_lock(&rxnet->conn_lock); | 
 | 		list_add_tail(&conn->link, &rxnet->service_conns); | 
 | 		list_add_tail(&conn->proc_link, &rxnet->conn_proc_list); | 
 | 		write_unlock(&rxnet->conn_lock); | 
 |  | 
 | 		rxrpc_see_connection(conn, rxrpc_conn_new_service); | 
 | 	} | 
 |  | 
 | 	return conn; | 
 | } | 
 |  | 
 | /* | 
 |  * Set up an incoming connection.  This is called in BH context with the RCU | 
 |  * read lock held. | 
 |  */ | 
 | void rxrpc_new_incoming_connection(struct rxrpc_sock *rx, | 
 | 				   struct rxrpc_connection *conn, | 
 | 				   const struct rxrpc_security *sec, | 
 | 				   struct sk_buff *skb) | 
 | { | 
 | 	struct rxrpc_skb_priv *sp = rxrpc_skb(skb); | 
 |  | 
 | 	_enter(""); | 
 |  | 
 | 	conn->proto.epoch	= sp->hdr.epoch; | 
 | 	conn->proto.cid		= sp->hdr.cid & RXRPC_CIDMASK; | 
 | 	conn->orig_service_id	= sp->hdr.serviceId; | 
 | 	conn->service_id	= sp->hdr.serviceId; | 
 | 	conn->security_ix	= sp->hdr.securityIndex; | 
 | 	conn->out_clientflag	= 0; | 
 | 	conn->security		= sec; | 
 | 	if (conn->security_ix) | 
 | 		conn->state	= RXRPC_CONN_SERVICE_UNSECURED; | 
 | 	else | 
 | 		conn->state	= RXRPC_CONN_SERVICE; | 
 |  | 
 | 	/* See if we should upgrade the service.  This can only happen on the | 
 | 	 * first packet on a new connection.  Once done, it applies to all | 
 | 	 * subsequent calls on that connection. | 
 | 	 */ | 
 | 	if (sp->hdr.userStatus == RXRPC_USERSTATUS_SERVICE_UPGRADE && | 
 | 	    conn->service_id == rx->service_upgrade.from) | 
 | 		conn->service_id = rx->service_upgrade.to; | 
 |  | 
 | 	atomic_set(&conn->active, 1); | 
 |  | 
 | 	/* Make the connection a target for incoming packets. */ | 
 | 	rxrpc_publish_service_conn(conn->peer, conn); | 
 | } | 
 |  | 
 | /* | 
 |  * Remove the service connection from the peer's tree, thereby removing it as a | 
 |  * target for incoming packets. | 
 |  */ | 
 | void rxrpc_unpublish_service_conn(struct rxrpc_connection *conn) | 
 | { | 
 | 	struct rxrpc_peer *peer = conn->peer; | 
 |  | 
 | 	write_seqlock(&peer->service_conn_lock); | 
 | 	if (test_and_clear_bit(RXRPC_CONN_IN_SERVICE_CONNS, &conn->flags)) | 
 | 		rb_erase(&conn->service_node, &peer->service_conns); | 
 | 	write_sequnlock(&peer->service_conn_lock); | 
 | } |