| // 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); | |
| } |