| // SPDX-License-Identifier: GPL-2.0-or-later | 
 | /* GSSAPI-based RxRPC security | 
 |  * | 
 |  * 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/skbuff.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/key-type.h> | 
 | #include "ar-internal.h" | 
 | #include "rxgk_common.h" | 
 |  | 
 | /* | 
 |  * Parse the information from a server key | 
 |  */ | 
 | static int rxgk_preparse_server_key(struct key_preparsed_payload *prep) | 
 | { | 
 | 	const struct krb5_enctype *krb5; | 
 | 	struct krb5_buffer *server_key = (void *)&prep->payload.data[2]; | 
 | 	unsigned int service, sec_class, kvno, enctype; | 
 | 	int n = 0; | 
 |  | 
 | 	_enter("%zu", prep->datalen); | 
 |  | 
 | 	if (sscanf(prep->orig_description, "%u:%u:%u:%u%n", | 
 | 		   &service, &sec_class, &kvno, &enctype, &n) != 4) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (prep->orig_description[n]) | 
 | 		return -EINVAL; | 
 |  | 
 | 	krb5 = crypto_krb5_find_enctype(enctype); | 
 | 	if (!krb5) | 
 | 		return -ENOPKG; | 
 |  | 
 | 	prep->payload.data[0] = (struct krb5_enctype *)krb5; | 
 |  | 
 | 	if (prep->datalen != krb5->key_len) | 
 | 		return -EKEYREJECTED; | 
 |  | 
 | 	server_key->len = prep->datalen; | 
 | 	server_key->data = kmemdup(prep->data, prep->datalen, GFP_KERNEL); | 
 | 	if (!server_key->data) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	_leave(" = 0"); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void rxgk_free_server_key(union key_payload *payload) | 
 | { | 
 | 	struct krb5_buffer *server_key = (void *)&payload->data[2]; | 
 |  | 
 | 	kfree_sensitive(server_key->data); | 
 | } | 
 |  | 
 | static void rxgk_free_preparse_server_key(struct key_preparsed_payload *prep) | 
 | { | 
 | 	rxgk_free_server_key(&prep->payload); | 
 | } | 
 |  | 
 | static void rxgk_destroy_server_key(struct key *key) | 
 | { | 
 | 	rxgk_free_server_key(&key->payload); | 
 | } | 
 |  | 
 | static void rxgk_describe_server_key(const struct key *key, struct seq_file *m) | 
 | { | 
 | 	const struct krb5_enctype *krb5 = key->payload.data[0]; | 
 |  | 
 | 	if (krb5) | 
 | 		seq_printf(m, ": %s", krb5->name); | 
 | } | 
 |  | 
 | /* | 
 |  * Handle rekeying the connection when we see our limits overrun or when the | 
 |  * far side decided to rekey. | 
 |  * | 
 |  * Returns a ref on the context if successful or -ESTALE if the key is out of | 
 |  * date. | 
 |  */ | 
 | static struct rxgk_context *rxgk_rekey(struct rxrpc_connection *conn, | 
 | 				       const u16 *specific_key_number) | 
 | { | 
 | 	struct rxgk_context *gk, *dead = NULL; | 
 | 	unsigned int key_number, current_key, mask = ARRAY_SIZE(conn->rxgk.keys) - 1; | 
 | 	bool crank = false; | 
 |  | 
 | 	_enter("%d", specific_key_number ? *specific_key_number : -1); | 
 |  | 
 | 	mutex_lock(&conn->security_lock); | 
 |  | 
 | 	current_key = conn->rxgk.key_number; | 
 | 	if (!specific_key_number) { | 
 | 		key_number = current_key; | 
 | 	} else { | 
 | 		if (*specific_key_number == (u16)current_key) | 
 | 			key_number = current_key; | 
 | 		else if (*specific_key_number == (u16)(current_key - 1)) | 
 | 			key_number = current_key - 1; | 
 | 		else if (*specific_key_number == (u16)(current_key + 1)) | 
 | 			goto crank_window; | 
 | 		else | 
 | 			goto bad_key; | 
 | 	} | 
 |  | 
 | 	gk = conn->rxgk.keys[key_number & mask]; | 
 | 	if (!gk) | 
 | 		goto generate_key; | 
 | 	if (!specific_key_number && | 
 | 	    test_bit(RXGK_TK_NEEDS_REKEY, &gk->flags)) | 
 | 		goto crank_window; | 
 |  | 
 | grab: | 
 | 	refcount_inc(&gk->usage); | 
 | 	mutex_unlock(&conn->security_lock); | 
 | 	rxgk_put(dead); | 
 | 	return gk; | 
 |  | 
 | crank_window: | 
 | 	trace_rxrpc_rxgk_rekey(conn, current_key, | 
 | 			       specific_key_number ? *specific_key_number : -1); | 
 | 	if (current_key == UINT_MAX) | 
 | 		goto bad_key; | 
 | 	if (current_key + 1 == UINT_MAX) | 
 | 		set_bit(RXRPC_CONN_DONT_REUSE, &conn->flags); | 
 |  | 
 | 	key_number = current_key + 1; | 
 | 	if (WARN_ON(conn->rxgk.keys[key_number & mask])) | 
 | 		goto bad_key; | 
 | 	crank = true; | 
 |  | 
 | generate_key: | 
 | 	gk = conn->rxgk.keys[current_key & mask]; | 
 | 	gk = rxgk_generate_transport_key(conn, gk->key, key_number, GFP_NOFS); | 
 | 	if (IS_ERR(gk)) { | 
 | 		mutex_unlock(&conn->security_lock); | 
 | 		return gk; | 
 | 	} | 
 |  | 
 | 	write_lock(&conn->security_use_lock); | 
 | 	if (crank) { | 
 | 		current_key++; | 
 | 		conn->rxgk.key_number = current_key; | 
 | 		dead = conn->rxgk.keys[(current_key - 2) & mask]; | 
 | 		conn->rxgk.keys[(current_key - 2) & mask] = NULL; | 
 | 	} | 
 | 	conn->rxgk.keys[current_key & mask] = gk; | 
 | 	write_unlock(&conn->security_use_lock); | 
 | 	goto grab; | 
 |  | 
 | bad_key: | 
 | 	mutex_unlock(&conn->security_lock); | 
 | 	return ERR_PTR(-ESTALE); | 
 | } | 
 |  | 
 | /* | 
 |  * Get the specified keying context. | 
 |  * | 
 |  * Returns a ref on the context if successful or -ESTALE if the key is out of | 
 |  * date. | 
 |  */ | 
 | static struct rxgk_context *rxgk_get_key(struct rxrpc_connection *conn, | 
 | 					 const u16 *specific_key_number) | 
 | { | 
 | 	struct rxgk_context *gk; | 
 | 	unsigned int key_number, current_key, mask = ARRAY_SIZE(conn->rxgk.keys) - 1; | 
 |  | 
 | 	_enter("{%u},%d", | 
 | 	       conn->rxgk.key_number, specific_key_number ? *specific_key_number : -1); | 
 |  | 
 | 	read_lock(&conn->security_use_lock); | 
 |  | 
 | 	current_key = conn->rxgk.key_number; | 
 | 	if (!specific_key_number) { | 
 | 		key_number = current_key; | 
 | 	} else { | 
 | 		/* Only the bottom 16 bits of the key number are exposed in the | 
 | 		 * header, so we try and keep the upper 16 bits in step.  The | 
 | 		 * whole 32 bits are used to generate the TK. | 
 | 		 */ | 
 | 		if (*specific_key_number == (u16)current_key) | 
 | 			key_number = current_key; | 
 | 		else if (*specific_key_number == (u16)(current_key - 1)) | 
 | 			key_number = current_key - 1; | 
 | 		else if (*specific_key_number == (u16)(current_key + 1)) | 
 | 			goto rekey; | 
 | 		else | 
 | 			goto bad_key; | 
 | 	} | 
 |  | 
 | 	gk = conn->rxgk.keys[key_number & mask]; | 
 | 	if (!gk) | 
 | 		goto slow_path; | 
 | 	if (!specific_key_number && | 
 | 	    key_number < UINT_MAX) { | 
 | 		if (time_after(jiffies, gk->expiry) || | 
 | 		    gk->bytes_remaining < 0) { | 
 | 			set_bit(RXGK_TK_NEEDS_REKEY, &gk->flags); | 
 | 			goto slow_path; | 
 | 		} | 
 |  | 
 | 		if (test_bit(RXGK_TK_NEEDS_REKEY, &gk->flags)) | 
 | 			goto slow_path; | 
 | 	} | 
 |  | 
 | 	refcount_inc(&gk->usage); | 
 | 	read_unlock(&conn->security_use_lock); | 
 | 	return gk; | 
 |  | 
 | rekey: | 
 | 	_debug("rekey"); | 
 | 	if (current_key == UINT_MAX) | 
 | 		goto bad_key; | 
 | 	gk = conn->rxgk.keys[current_key & mask]; | 
 | 	if (gk) | 
 | 		set_bit(RXGK_TK_NEEDS_REKEY, &gk->flags); | 
 | slow_path: | 
 | 	read_unlock(&conn->security_use_lock); | 
 | 	return rxgk_rekey(conn, specific_key_number); | 
 | bad_key: | 
 | 	read_unlock(&conn->security_use_lock); | 
 | 	return ERR_PTR(-ESTALE); | 
 | } | 
 |  | 
 | /* | 
 |  * initialise connection security | 
 |  */ | 
 | static int rxgk_init_connection_security(struct rxrpc_connection *conn, | 
 | 					 struct rxrpc_key_token *token) | 
 | { | 
 | 	struct rxgk_context *gk; | 
 | 	int ret; | 
 |  | 
 | 	_enter("{%d,%u},{%x}", | 
 | 	       conn->debug_id, conn->rxgk.key_number, key_serial(conn->key)); | 
 |  | 
 | 	conn->security_ix = token->security_index; | 
 | 	conn->security_level = token->rxgk->level; | 
 |  | 
 | 	if (rxrpc_conn_is_client(conn)) { | 
 | 		conn->rxgk.start_time = ktime_get(); | 
 | 		do_div(conn->rxgk.start_time, 100); | 
 | 	} | 
 |  | 
 | 	gk = rxgk_generate_transport_key(conn, token->rxgk, conn->rxgk.key_number, | 
 | 					 GFP_NOFS); | 
 | 	if (IS_ERR(gk)) | 
 | 		return PTR_ERR(gk); | 
 | 	conn->rxgk.enctype = gk->krb5->etype; | 
 | 	conn->rxgk.keys[gk->key_number & 3] = gk; | 
 |  | 
 | 	switch (conn->security_level) { | 
 | 	case RXRPC_SECURITY_PLAIN: | 
 | 	case RXRPC_SECURITY_AUTH: | 
 | 	case RXRPC_SECURITY_ENCRYPT: | 
 | 		break; | 
 | 	default: | 
 | 		ret = -EKEYREJECTED; | 
 | 		goto error; | 
 | 	} | 
 |  | 
 | 	ret = 0; | 
 | error: | 
 | 	_leave(" = %d", ret); | 
 | 	return ret; | 
 | } | 
 |  | 
 | /* | 
 |  * Clean up the crypto on a call. | 
 |  */ | 
 | static void rxgk_free_call_crypto(struct rxrpc_call *call) | 
 | { | 
 | } | 
 |  | 
 | /* | 
 |  * Work out how much data we can put in a packet. | 
 |  */ | 
 | static struct rxrpc_txbuf *rxgk_alloc_txbuf(struct rxrpc_call *call, size_t remain, gfp_t gfp) | 
 | { | 
 | 	enum krb5_crypto_mode mode; | 
 | 	struct rxgk_context *gk; | 
 | 	struct rxrpc_txbuf *txb; | 
 | 	size_t shdr, alloc, limit, part, offset, gap; | 
 |  | 
 | 	switch (call->conn->security_level) { | 
 | 	default: | 
 | 		alloc = umin(remain, RXRPC_JUMBO_DATALEN); | 
 | 		return rxrpc_alloc_data_txbuf(call, alloc, 1, gfp); | 
 | 	case RXRPC_SECURITY_AUTH: | 
 | 		shdr = 0; | 
 | 		mode = KRB5_CHECKSUM_MODE; | 
 | 		break; | 
 | 	case RXRPC_SECURITY_ENCRYPT: | 
 | 		shdr = sizeof(struct rxgk_header); | 
 | 		mode = KRB5_ENCRYPT_MODE; | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	gk = rxgk_get_key(call->conn, NULL); | 
 | 	if (IS_ERR(gk)) | 
 | 		return NULL; | 
 |  | 
 | 	/* Work out the maximum amount of data that will fit. */ | 
 | 	alloc = RXRPC_JUMBO_DATALEN; | 
 | 	limit = crypto_krb5_how_much_data(gk->krb5, mode, &alloc, &offset); | 
 |  | 
 | 	if (remain < limit - shdr) { | 
 | 		part = remain; | 
 | 		alloc = crypto_krb5_how_much_buffer(gk->krb5, mode, | 
 | 						    shdr + part, &offset); | 
 | 		gap = 0; | 
 | 	} else { | 
 | 		part = limit - shdr; | 
 | 		gap = RXRPC_JUMBO_DATALEN - alloc; | 
 | 		alloc = RXRPC_JUMBO_DATALEN; | 
 | 	} | 
 |  | 
 | 	rxgk_put(gk); | 
 |  | 
 | 	txb = rxrpc_alloc_data_txbuf(call, alloc, 16, gfp); | 
 | 	if (!txb) | 
 | 		return NULL; | 
 |  | 
 | 	txb->crypto_header	= offset; | 
 | 	txb->sec_header		= shdr; | 
 | 	txb->offset		+= offset + shdr; | 
 | 	txb->space		= part; | 
 |  | 
 | 	/* Clear excess space in the packet */ | 
 | 	if (gap) | 
 | 		memset(txb->data + alloc - gap, 0, gap); | 
 | 	return txb; | 
 | } | 
 |  | 
 | /* | 
 |  * Integrity mode (sign a packet - level 1 security) | 
 |  */ | 
 | static int rxgk_secure_packet_integrity(const struct rxrpc_call *call, | 
 | 					struct rxgk_context *gk, | 
 | 					struct rxrpc_txbuf *txb) | 
 | { | 
 | 	struct rxgk_header *hdr; | 
 | 	struct scatterlist sg[1]; | 
 | 	struct krb5_buffer metadata; | 
 | 	int ret = -ENOMEM; | 
 |  | 
 | 	_enter(""); | 
 |  | 
 | 	hdr = kzalloc(sizeof(*hdr), GFP_NOFS); | 
 | 	if (!hdr) | 
 | 		goto error_gk; | 
 |  | 
 | 	hdr->epoch	= htonl(call->conn->proto.epoch); | 
 | 	hdr->cid	= htonl(call->cid); | 
 | 	hdr->call_number = htonl(call->call_id); | 
 | 	hdr->seq	= htonl(txb->seq); | 
 | 	hdr->sec_index	= htonl(call->security_ix); | 
 | 	hdr->data_len	= htonl(txb->len); | 
 | 	metadata.len = sizeof(*hdr); | 
 | 	metadata.data = hdr; | 
 |  | 
 | 	sg_init_table(sg, 1); | 
 | 	sg_set_buf(&sg[0], txb->data, txb->alloc_size); | 
 |  | 
 | 	ret = crypto_krb5_get_mic(gk->krb5, gk->tx_Kc, &metadata, | 
 | 				  sg, 1, txb->alloc_size, | 
 | 				  txb->crypto_header, txb->sec_header + txb->len); | 
 | 	if (ret >= 0) { | 
 | 		txb->pkt_len = ret; | 
 | 		if (txb->alloc_size == RXRPC_JUMBO_DATALEN) | 
 | 			txb->jumboable = true; | 
 | 		gk->bytes_remaining -= ret; | 
 | 	} | 
 | 	kfree(hdr); | 
 | error_gk: | 
 | 	rxgk_put(gk); | 
 | 	_leave(" = %d", ret); | 
 | 	return ret; | 
 | } | 
 |  | 
 | /* | 
 |  * wholly encrypt a packet (level 2 security) | 
 |  */ | 
 | static int rxgk_secure_packet_encrypted(const struct rxrpc_call *call, | 
 | 					struct rxgk_context *gk, | 
 | 					struct rxrpc_txbuf *txb) | 
 | { | 
 | 	struct rxgk_header *hdr; | 
 | 	struct scatterlist sg[1]; | 
 | 	int ret; | 
 |  | 
 | 	_enter("%x", txb->len); | 
 |  | 
 | 	/* Insert the header into the buffer. */ | 
 | 	hdr = txb->data + txb->crypto_header; | 
 | 	hdr->epoch	 = htonl(call->conn->proto.epoch); | 
 | 	hdr->cid	 = htonl(call->cid); | 
 | 	hdr->call_number = htonl(call->call_id); | 
 | 	hdr->seq	 = htonl(txb->seq); | 
 | 	hdr->sec_index	 = htonl(call->security_ix); | 
 | 	hdr->data_len	 = htonl(txb->len); | 
 |  | 
 | 	sg_init_table(sg, 1); | 
 | 	sg_set_buf(&sg[0], txb->data, txb->alloc_size); | 
 |  | 
 | 	ret = crypto_krb5_encrypt(gk->krb5, gk->tx_enc, | 
 | 				  sg, 1, txb->alloc_size, | 
 | 				  txb->crypto_header, txb->sec_header + txb->len, | 
 | 				  false); | 
 | 	if (ret >= 0) { | 
 | 		txb->pkt_len = ret; | 
 | 		if (txb->alloc_size == RXRPC_JUMBO_DATALEN) | 
 | 			txb->jumboable = true; | 
 | 		gk->bytes_remaining -= ret; | 
 | 	} | 
 |  | 
 | 	rxgk_put(gk); | 
 | 	_leave(" = %d", ret); | 
 | 	return ret; | 
 | } | 
 |  | 
 | /* | 
 |  * checksum an RxRPC packet header | 
 |  */ | 
 | static int rxgk_secure_packet(struct rxrpc_call *call, struct rxrpc_txbuf *txb) | 
 | { | 
 | 	struct rxgk_context *gk; | 
 | 	int ret; | 
 |  | 
 | 	_enter("{%d{%x}},{#%u},%u,", | 
 | 	       call->debug_id, key_serial(call->conn->key), txb->seq, txb->len); | 
 |  | 
 | 	gk = rxgk_get_key(call->conn, NULL); | 
 | 	if (IS_ERR(gk)) | 
 | 		return PTR_ERR(gk) == -ESTALE ? -EKEYREJECTED : PTR_ERR(gk); | 
 |  | 
 | 	ret = key_validate(call->conn->key); | 
 | 	if (ret < 0) { | 
 | 		rxgk_put(gk); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	call->security_enctype = gk->krb5->etype; | 
 | 	txb->cksum = htons(gk->key_number); | 
 |  | 
 | 	switch (call->conn->security_level) { | 
 | 	case RXRPC_SECURITY_PLAIN: | 
 | 		rxgk_put(gk); | 
 | 		txb->pkt_len = txb->len; | 
 | 		return 0; | 
 | 	case RXRPC_SECURITY_AUTH: | 
 | 		return rxgk_secure_packet_integrity(call, gk, txb); | 
 | 	case RXRPC_SECURITY_ENCRYPT: | 
 | 		return rxgk_secure_packet_encrypted(call, gk, txb); | 
 | 	default: | 
 | 		rxgk_put(gk); | 
 | 		return -EPERM; | 
 | 	} | 
 | } | 
 |  | 
 | /* | 
 |  * Integrity mode (check the signature on a packet - level 1 security) | 
 |  */ | 
 | static int rxgk_verify_packet_integrity(struct rxrpc_call *call, | 
 | 					struct rxgk_context *gk, | 
 | 					struct sk_buff *skb) | 
 | { | 
 | 	struct rxrpc_skb_priv *sp = rxrpc_skb(skb); | 
 | 	struct rxgk_header *hdr; | 
 | 	struct krb5_buffer metadata; | 
 | 	unsigned int offset = sp->offset, len = sp->len; | 
 | 	size_t data_offset = 0, data_len = len; | 
 | 	u32 ac = 0; | 
 | 	int ret = -ENOMEM; | 
 |  | 
 | 	_enter(""); | 
 |  | 
 | 	crypto_krb5_where_is_the_data(gk->krb5, KRB5_CHECKSUM_MODE, | 
 | 				      &data_offset, &data_len); | 
 |  | 
 | 	hdr = kzalloc(sizeof(*hdr), GFP_NOFS); | 
 | 	if (!hdr) | 
 | 		goto put_gk; | 
 |  | 
 | 	hdr->epoch	= htonl(call->conn->proto.epoch); | 
 | 	hdr->cid	= htonl(call->cid); | 
 | 	hdr->call_number = htonl(call->call_id); | 
 | 	hdr->seq	= htonl(sp->hdr.seq); | 
 | 	hdr->sec_index	= htonl(call->security_ix); | 
 | 	hdr->data_len	= htonl(data_len); | 
 |  | 
 | 	metadata.len = sizeof(*hdr); | 
 | 	metadata.data = hdr; | 
 | 	ret = rxgk_verify_mic_skb(gk->krb5, gk->rx_Kc, &metadata, | 
 | 				  skb, &offset, &len, &ac); | 
 | 	kfree(hdr); | 
 | 	if (ret < 0) { | 
 | 		if (ret != -ENOMEM) | 
 | 			rxrpc_abort_eproto(call, skb, ac, | 
 | 					   rxgk_abort_1_verify_mic_eproto); | 
 | 	} else { | 
 | 		sp->offset = offset; | 
 | 		sp->len = len; | 
 | 	} | 
 |  | 
 | put_gk: | 
 | 	rxgk_put(gk); | 
 | 	_leave(" = %d", ret); | 
 | 	return ret; | 
 | } | 
 |  | 
 | /* | 
 |  * Decrypt an encrypted packet (level 2 security). | 
 |  */ | 
 | static int rxgk_verify_packet_encrypted(struct rxrpc_call *call, | 
 | 					struct rxgk_context *gk, | 
 | 					struct sk_buff *skb) | 
 | { | 
 | 	struct rxrpc_skb_priv *sp = rxrpc_skb(skb); | 
 | 	struct rxgk_header hdr; | 
 | 	unsigned int offset = sp->offset, len = sp->len; | 
 | 	int ret; | 
 | 	u32 ac = 0; | 
 |  | 
 | 	_enter(""); | 
 |  | 
 | 	ret = rxgk_decrypt_skb(gk->krb5, gk->rx_enc, skb, &offset, &len, &ac); | 
 | 	if (ret < 0) { | 
 | 		if (ret != -ENOMEM) | 
 | 			rxrpc_abort_eproto(call, skb, ac, rxgk_abort_2_decrypt_eproto); | 
 | 		goto error; | 
 | 	} | 
 |  | 
 | 	if (len < sizeof(hdr)) { | 
 | 		ret = rxrpc_abort_eproto(call, skb, RXGK_PACKETSHORT, | 
 | 					 rxgk_abort_2_short_header); | 
 | 		goto error; | 
 | 	} | 
 |  | 
 | 	/* Extract the header from the skb */ | 
 | 	ret = skb_copy_bits(skb, offset, &hdr, sizeof(hdr)); | 
 | 	if (ret < 0) { | 
 | 		ret = rxrpc_abort_eproto(call, skb, RXGK_PACKETSHORT, | 
 | 					 rxgk_abort_2_short_encdata); | 
 | 		goto error; | 
 | 	} | 
 | 	offset += sizeof(hdr); | 
 | 	len -= sizeof(hdr); | 
 |  | 
 | 	if (ntohl(hdr.epoch)		!= call->conn->proto.epoch || | 
 | 	    ntohl(hdr.cid)		!= call->cid || | 
 | 	    ntohl(hdr.call_number)	!= call->call_id || | 
 | 	    ntohl(hdr.seq)		!= sp->hdr.seq || | 
 | 	    ntohl(hdr.sec_index)	!= call->security_ix || | 
 | 	    ntohl(hdr.data_len)		> len) { | 
 | 		ret = rxrpc_abort_eproto(call, skb, RXGK_SEALEDINCON, | 
 | 					 rxgk_abort_2_short_data); | 
 | 		goto error; | 
 | 	} | 
 |  | 
 | 	sp->offset = offset; | 
 | 	sp->len = ntohl(hdr.data_len); | 
 | 	ret = 0; | 
 | error: | 
 | 	rxgk_put(gk); | 
 | 	_leave(" = %d", ret); | 
 | 	return ret; | 
 | } | 
 |  | 
 | /* | 
 |  * Verify the security on a received packet or subpacket (if part of a | 
 |  * jumbo packet). | 
 |  */ | 
 | static int rxgk_verify_packet(struct rxrpc_call *call, struct sk_buff *skb) | 
 | { | 
 | 	struct rxrpc_skb_priv *sp = rxrpc_skb(skb); | 
 | 	struct rxgk_context *gk; | 
 | 	u16 key_number = sp->hdr.cksum; | 
 |  | 
 | 	_enter("{%d{%x}},{#%u}", | 
 | 	       call->debug_id, key_serial(call->conn->key), sp->hdr.seq); | 
 |  | 
 | 	gk = rxgk_get_key(call->conn, &key_number); | 
 | 	if (IS_ERR(gk)) { | 
 | 		switch (PTR_ERR(gk)) { | 
 | 		case -ESTALE: | 
 | 			return rxrpc_abort_eproto(call, skb, RXGK_BADKEYNO, | 
 | 						  rxgk_abort_bad_key_number); | 
 | 		default: | 
 | 			return PTR_ERR(gk); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	call->security_enctype = gk->krb5->etype; | 
 | 	switch (call->conn->security_level) { | 
 | 	case RXRPC_SECURITY_PLAIN: | 
 | 		rxgk_put(gk); | 
 | 		return 0; | 
 | 	case RXRPC_SECURITY_AUTH: | 
 | 		return rxgk_verify_packet_integrity(call, gk, skb); | 
 | 	case RXRPC_SECURITY_ENCRYPT: | 
 | 		return rxgk_verify_packet_encrypted(call, gk, skb); | 
 | 	default: | 
 | 		rxgk_put(gk); | 
 | 		return -ENOANO; | 
 | 	} | 
 | } | 
 |  | 
 | /* | 
 |  * Allocate memory to hold a challenge or a response packet.  We're not running | 
 |  * in the io_thread, so we can't use ->tx_alloc. | 
 |  */ | 
 | static struct page *rxgk_alloc_packet(size_t total_len) | 
 | { | 
 | 	gfp_t gfp = GFP_NOFS; | 
 | 	int order; | 
 |  | 
 | 	order = get_order(total_len); | 
 | 	if (order > 0) | 
 | 		gfp |= __GFP_COMP; | 
 | 	return alloc_pages(gfp, order); | 
 | } | 
 |  | 
 | /* | 
 |  * Issue a challenge. | 
 |  */ | 
 | static int rxgk_issue_challenge(struct rxrpc_connection *conn) | 
 | { | 
 | 	struct rxrpc_wire_header *whdr; | 
 | 	struct bio_vec bvec[1]; | 
 | 	struct msghdr msg; | 
 | 	struct page *page; | 
 | 	size_t len = sizeof(*whdr) + sizeof(conn->rxgk.nonce); | 
 | 	u32 serial; | 
 | 	int ret; | 
 |  | 
 | 	_enter("{%d}", conn->debug_id); | 
 |  | 
 | 	get_random_bytes(&conn->rxgk.nonce, sizeof(conn->rxgk.nonce)); | 
 |  | 
 | 	/* We can't use conn->tx_alloc without a lock */ | 
 | 	page = rxgk_alloc_packet(sizeof(*whdr) + sizeof(conn->rxgk.nonce)); | 
 | 	if (!page) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	bvec_set_page(&bvec[0], page, len, 0); | 
 | 	iov_iter_bvec(&msg.msg_iter, WRITE, bvec, 1, len); | 
 |  | 
 | 	msg.msg_name	= &conn->peer->srx.transport; | 
 | 	msg.msg_namelen	= conn->peer->srx.transport_len; | 
 | 	msg.msg_control	= NULL; | 
 | 	msg.msg_controllen = 0; | 
 | 	msg.msg_flags	= MSG_SPLICE_PAGES; | 
 |  | 
 | 	whdr = page_address(page); | 
 | 	whdr->epoch	= htonl(conn->proto.epoch); | 
 | 	whdr->cid	= htonl(conn->proto.cid); | 
 | 	whdr->callNumber = 0; | 
 | 	whdr->seq	= 0; | 
 | 	whdr->type	= RXRPC_PACKET_TYPE_CHALLENGE; | 
 | 	whdr->flags	= conn->out_clientflag; | 
 | 	whdr->userStatus = 0; | 
 | 	whdr->securityIndex = conn->security_ix; | 
 | 	whdr->_rsvd	= 0; | 
 | 	whdr->serviceId	= htons(conn->service_id); | 
 |  | 
 | 	memcpy(whdr + 1, conn->rxgk.nonce, sizeof(conn->rxgk.nonce)); | 
 |  | 
 | 	serial = rxrpc_get_next_serials(conn, 1); | 
 | 	whdr->serial = htonl(serial); | 
 |  | 
 | 	trace_rxrpc_tx_challenge(conn, serial, 0, *(u32 *)&conn->rxgk.nonce); | 
 |  | 
 | 	ret = do_udp_sendmsg(conn->local->socket, &msg, len); | 
 | 	if (ret > 0) | 
 | 		conn->peer->last_tx_at = ktime_get_seconds(); | 
 | 	__free_page(page); | 
 |  | 
 | 	if (ret < 0) { | 
 | 		trace_rxrpc_tx_fail(conn->debug_id, serial, ret, | 
 | 				    rxrpc_tx_point_rxgk_challenge); | 
 | 		return -EAGAIN; | 
 | 	} | 
 |  | 
 | 	trace_rxrpc_tx_packet(conn->debug_id, whdr, | 
 | 			      rxrpc_tx_point_rxgk_challenge); | 
 | 	_leave(" = 0"); | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  * Validate a challenge packet. | 
 |  */ | 
 | static bool rxgk_validate_challenge(struct rxrpc_connection *conn, | 
 | 				    struct sk_buff *skb) | 
 | { | 
 | 	struct rxrpc_skb_priv *sp = rxrpc_skb(skb); | 
 | 	u8 nonce[20]; | 
 |  | 
 | 	if (!conn->key) { | 
 | 		rxrpc_abort_conn(conn, skb, RX_PROTOCOL_ERROR, -EPROTO, | 
 | 				 rxgk_abort_chall_no_key); | 
 | 		return false; | 
 | 	} | 
 |  | 
 | 	if (key_validate(conn->key) < 0) { | 
 | 		rxrpc_abort_conn(conn, skb, RXGK_EXPIRED, -EPROTO, | 
 | 				 rxgk_abort_chall_key_expired); | 
 | 		return false; | 
 | 	} | 
 |  | 
 | 	if (skb_copy_bits(skb, sizeof(struct rxrpc_wire_header), | 
 | 			  nonce, sizeof(nonce)) < 0) { | 
 | 		rxrpc_abort_conn(conn, skb, RXGK_PACKETSHORT, -EPROTO, | 
 | 				 rxgk_abort_chall_short); | 
 | 		return false; | 
 | 	} | 
 |  | 
 | 	trace_rxrpc_rx_challenge(conn, sp->hdr.serial, 0, *(u32 *)nonce, 0); | 
 | 	return true; | 
 | } | 
 |  | 
 | /** | 
 |  * rxgk_kernel_query_challenge - Query RxGK-specific challenge parameters | 
 |  * @challenge: The challenge packet to query | 
 |  * | 
 |  * Return: The Kerberos 5 encoding type for the challenged connection. | 
 |  */ | 
 | u32 rxgk_kernel_query_challenge(struct sk_buff *challenge) | 
 | { | 
 | 	struct rxrpc_skb_priv *sp = rxrpc_skb(challenge); | 
 |  | 
 | 	return sp->chall.conn->rxgk.enctype; | 
 | } | 
 | EXPORT_SYMBOL(rxgk_kernel_query_challenge); | 
 |  | 
 | /* | 
 |  * Fill out the control message to pass to userspace to inform about the | 
 |  * challenge. | 
 |  */ | 
 | static int rxgk_challenge_to_recvmsg(struct rxrpc_connection *conn, | 
 | 				     struct sk_buff *challenge, | 
 | 				     struct msghdr *msg) | 
 | { | 
 | 	struct rxgk_challenge chall; | 
 |  | 
 | 	chall.base.service_id		= conn->service_id; | 
 | 	chall.base.security_index	= conn->security_ix; | 
 | 	chall.enctype			= conn->rxgk.enctype; | 
 |  | 
 | 	return put_cmsg(msg, SOL_RXRPC, RXRPC_CHALLENGED, sizeof(chall), &chall); | 
 | } | 
 |  | 
 | /* | 
 |  * Insert the requisite amount of XDR padding for the length given. | 
 |  */ | 
 | static int rxgk_pad_out(struct sk_buff *response, size_t len, size_t offset) | 
 | { | 
 | 	__be32 zero = 0; | 
 | 	size_t pad = xdr_round_up(len) - len; | 
 | 	int ret; | 
 |  | 
 | 	if (!pad) | 
 | 		return 0; | 
 |  | 
 | 	ret = skb_store_bits(response, offset, &zero, pad); | 
 | 	if (ret < 0) | 
 | 		return ret; | 
 | 	return pad; | 
 | } | 
 |  | 
 | /* | 
 |  * Insert the header into the response. | 
 |  */ | 
 | static noinline ssize_t rxgk_insert_response_header(struct rxrpc_connection *conn, | 
 | 						    struct rxgk_context *gk, | 
 | 						    struct sk_buff *response, | 
 | 						    size_t offset) | 
 | { | 
 | 	struct rxrpc_skb_priv *rsp = rxrpc_skb(response); | 
 |  | 
 | 	struct { | 
 | 		struct rxrpc_wire_header whdr; | 
 | 		__be32 start_time_msw; | 
 | 		__be32 start_time_lsw; | 
 | 		__be32 ticket_len; | 
 | 	} h; | 
 | 	int ret; | 
 |  | 
 | 	rsp->resp.kvno		= gk->key_number; | 
 | 	rsp->resp.version	= gk->krb5->etype; | 
 |  | 
 | 	h.whdr.epoch		= htonl(conn->proto.epoch); | 
 | 	h.whdr.cid		= htonl(conn->proto.cid); | 
 | 	h.whdr.callNumber	= 0; | 
 | 	h.whdr.serial		= 0; | 
 | 	h.whdr.seq		= 0; | 
 | 	h.whdr.type		= RXRPC_PACKET_TYPE_RESPONSE; | 
 | 	h.whdr.flags		= conn->out_clientflag; | 
 | 	h.whdr.userStatus	= 0; | 
 | 	h.whdr.securityIndex	= conn->security_ix; | 
 | 	h.whdr.cksum		= htons(gk->key_number); | 
 | 	h.whdr.serviceId	= htons(conn->service_id); | 
 | 	h.start_time_msw	= htonl(upper_32_bits(conn->rxgk.start_time)); | 
 | 	h.start_time_lsw	= htonl(lower_32_bits(conn->rxgk.start_time)); | 
 | 	h.ticket_len		= htonl(gk->key->ticket.len); | 
 |  | 
 | 	ret = skb_store_bits(response, offset, &h, sizeof(h)); | 
 | 	return ret < 0 ? ret : sizeof(h); | 
 | } | 
 |  | 
 | /* | 
 |  * Construct the authenticator to go in the response packet | 
 |  * | 
 |  * struct RXGK_Authenticator { | 
 |  *	opaque nonce[20]; | 
 |  *	opaque appdata<>; | 
 |  *	RXGK_Level level; | 
 |  *	unsigned int epoch; | 
 |  *	unsigned int cid; | 
 |  *	unsigned int call_numbers<>; | 
 |  * }; | 
 |  */ | 
 | static ssize_t rxgk_construct_authenticator(struct rxrpc_connection *conn, | 
 | 					    struct sk_buff *challenge, | 
 | 					    const struct krb5_buffer *appdata, | 
 | 					    struct sk_buff *response, | 
 | 					    size_t offset) | 
 | { | 
 | 	struct { | 
 | 		u8	nonce[20]; | 
 | 		__be32	appdata_len; | 
 | 	} a; | 
 | 	struct { | 
 | 		__be32	level; | 
 | 		__be32	epoch; | 
 | 		__be32	cid; | 
 | 		__be32	call_numbers_count; | 
 | 		__be32	call_numbers[4]; | 
 | 	} b; | 
 | 	int ret; | 
 |  | 
 | 	ret = skb_copy_bits(challenge, sizeof(struct rxrpc_wire_header), | 
 | 			    a.nonce, sizeof(a.nonce)); | 
 | 	if (ret < 0) | 
 | 		return -EPROTO; | 
 |  | 
 | 	a.appdata_len = htonl(appdata->len); | 
 |  | 
 | 	ret = skb_store_bits(response, offset, &a, sizeof(a)); | 
 | 	if (ret < 0) | 
 | 		return ret; | 
 | 	offset += sizeof(a); | 
 |  | 
 | 	if (appdata->len) { | 
 | 		ret = skb_store_bits(response, offset, appdata->data, appdata->len); | 
 | 		if (ret < 0) | 
 | 			return ret; | 
 | 		offset += appdata->len; | 
 |  | 
 | 		ret = rxgk_pad_out(response, appdata->len, offset); | 
 | 		if (ret < 0) | 
 | 			return ret; | 
 | 		offset += ret; | 
 | 	} | 
 |  | 
 | 	b.level			= htonl(conn->security_level); | 
 | 	b.epoch			= htonl(conn->proto.epoch); | 
 | 	b.cid			= htonl(conn->proto.cid); | 
 | 	b.call_numbers_count	= htonl(4); | 
 | 	b.call_numbers[0]	= htonl(conn->channels[0].call_counter); | 
 | 	b.call_numbers[1]	= htonl(conn->channels[1].call_counter); | 
 | 	b.call_numbers[2]	= htonl(conn->channels[2].call_counter); | 
 | 	b.call_numbers[3]	= htonl(conn->channels[3].call_counter); | 
 |  | 
 | 	ret = skb_store_bits(response, offset, &b, sizeof(b)); | 
 | 	if (ret < 0) | 
 | 		return ret; | 
 | 	return sizeof(a) + xdr_round_up(appdata->len) + sizeof(b); | 
 | } | 
 |  | 
 | static ssize_t rxgk_encrypt_authenticator(struct rxrpc_connection *conn, | 
 | 					  struct rxgk_context *gk, | 
 | 					  struct sk_buff *response, | 
 | 					  size_t offset, | 
 | 					  size_t alloc_len, | 
 | 					  size_t auth_offset, | 
 | 					  size_t auth_len) | 
 | { | 
 | 	struct scatterlist sg[16]; | 
 | 	int nr_sg; | 
 |  | 
 | 	sg_init_table(sg, ARRAY_SIZE(sg)); | 
 | 	nr_sg = skb_to_sgvec(response, sg, offset, alloc_len); | 
 | 	if (unlikely(nr_sg < 0)) | 
 | 		return nr_sg; | 
 | 	return crypto_krb5_encrypt(gk->krb5, gk->resp_enc, sg, nr_sg, alloc_len, | 
 | 				   auth_offset, auth_len, false); | 
 | } | 
 |  | 
 | /* | 
 |  * Construct the response. | 
 |  * | 
 |  * struct RXGK_Response { | 
 |  *	rxgkTime start_time; | 
 |  *	RXGK_Data token; | 
 |  *	opaque authenticator<RXGK_MAXAUTHENTICATOR> | 
 |  * }; | 
 |  */ | 
 | static int rxgk_construct_response(struct rxrpc_connection *conn, | 
 | 				   struct sk_buff *challenge, | 
 | 				   struct krb5_buffer *appdata) | 
 | { | 
 | 	struct rxrpc_skb_priv *csp, *rsp; | 
 | 	struct rxgk_context *gk; | 
 | 	struct sk_buff *response; | 
 | 	size_t len, auth_len, authx_len, offset, auth_offset, authx_offset; | 
 | 	__be32 tmp; | 
 | 	int ret; | 
 |  | 
 | 	gk = rxgk_get_key(conn, NULL); | 
 | 	if (IS_ERR(gk)) | 
 | 		return PTR_ERR(gk); | 
 |  | 
 | 	auth_len = 20 + (4 + appdata->len) + 12 + (1 + 4) * 4; | 
 | 	authx_len = crypto_krb5_how_much_buffer(gk->krb5, KRB5_ENCRYPT_MODE, | 
 | 						auth_len, &auth_offset); | 
 | 	len = sizeof(struct rxrpc_wire_header) + | 
 | 		8 + (4 + xdr_round_up(gk->key->ticket.len)) + (4 + authx_len); | 
 |  | 
 | 	response = alloc_skb_with_frags(0, len, 0, &ret, GFP_NOFS); | 
 | 	if (!response) | 
 | 		goto error; | 
 | 	rxrpc_new_skb(response, rxrpc_skb_new_response_rxgk); | 
 | 	response->len = len; | 
 | 	response->data_len = len; | 
 |  | 
 | 	ret = rxgk_insert_response_header(conn, gk, response, 0); | 
 | 	if (ret < 0) | 
 | 		goto error; | 
 | 	offset = ret; | 
 |  | 
 | 	ret = skb_store_bits(response, offset, gk->key->ticket.data, gk->key->ticket.len); | 
 | 	if (ret < 0) | 
 | 		goto error; | 
 | 	offset += gk->key->ticket.len; | 
 | 	ret = rxgk_pad_out(response, gk->key->ticket.len, offset); | 
 | 	if (ret < 0) | 
 | 		goto error; | 
 |  | 
 | 	authx_offset = offset + ret + 4; /* Leave a gap for the length. */ | 
 |  | 
 | 	ret = rxgk_construct_authenticator(conn, challenge, appdata, response, | 
 | 					   authx_offset + auth_offset); | 
 | 	if (ret < 0) | 
 | 		goto error; | 
 | 	auth_len = ret; | 
 |  | 
 | 	ret = rxgk_encrypt_authenticator(conn, gk, response, | 
 | 					 authx_offset, authx_len, | 
 | 					 auth_offset, auth_len); | 
 | 	if (ret < 0) | 
 | 		goto error; | 
 | 	authx_len = ret; | 
 |  | 
 | 	tmp = htonl(authx_len); | 
 | 	ret = skb_store_bits(response, authx_offset - 4, &tmp, 4); | 
 | 	if (ret < 0) | 
 | 		goto error; | 
 |  | 
 | 	ret = rxgk_pad_out(response, authx_len, authx_offset + authx_len); | 
 | 	if (ret < 0) | 
 | 		goto error; | 
 | 	len = authx_offset + authx_len + ret; | 
 |  | 
 | 	if (len != response->len) { | 
 | 		response->len = len; | 
 | 		response->data_len = len; | 
 | 	} | 
 |  | 
 | 	csp = rxrpc_skb(challenge); | 
 | 	rsp = rxrpc_skb(response); | 
 | 	rsp->resp.len = len; | 
 | 	rsp->resp.challenge_serial = csp->hdr.serial; | 
 | 	rxrpc_post_response(conn, response); | 
 | 	response = NULL; | 
 | 	ret = 0; | 
 |  | 
 | error: | 
 | 	rxrpc_free_skb(response, rxrpc_skb_put_response); | 
 | 	rxgk_put(gk); | 
 | 	_leave(" = %d", ret); | 
 | 	return ret; | 
 | } | 
 |  | 
 | /* | 
 |  * Respond to a challenge packet. | 
 |  */ | 
 | static int rxgk_respond_to_challenge(struct rxrpc_connection *conn, | 
 | 				     struct sk_buff *challenge, | 
 | 				     struct krb5_buffer *appdata) | 
 | { | 
 | 	_enter("{%d,%x}", conn->debug_id, key_serial(conn->key)); | 
 |  | 
 | 	if (key_validate(conn->key) < 0) | 
 | 		return rxrpc_abort_conn(conn, NULL, RXGK_EXPIRED, -EPROTO, | 
 | 					rxgk_abort_chall_key_expired); | 
 |  | 
 | 	return rxgk_construct_response(conn, challenge, appdata); | 
 | } | 
 |  | 
 | static int rxgk_respond_to_challenge_no_appdata(struct rxrpc_connection *conn, | 
 | 						struct sk_buff *challenge) | 
 | { | 
 | 	struct krb5_buffer appdata = {}; | 
 |  | 
 | 	return rxgk_respond_to_challenge(conn, challenge, &appdata); | 
 | } | 
 |  | 
 | /** | 
 |  * rxgk_kernel_respond_to_challenge - Respond to a challenge with appdata | 
 |  * @challenge: The challenge to respond to | 
 |  * @appdata: The application data to include in the RESPONSE authenticator | 
 |  * | 
 |  * Allow a kernel application to respond to a CHALLENGE with application data | 
 |  * to be included in the RxGK RESPONSE Authenticator. | 
 |  * | 
 |  * Return: %0 if successful and a negative error code otherwise. | 
 |  */ | 
 | int rxgk_kernel_respond_to_challenge(struct sk_buff *challenge, | 
 | 				     struct krb5_buffer *appdata) | 
 | { | 
 | 	struct rxrpc_skb_priv *csp = rxrpc_skb(challenge); | 
 |  | 
 | 	return rxgk_respond_to_challenge(csp->chall.conn, challenge, appdata); | 
 | } | 
 | EXPORT_SYMBOL(rxgk_kernel_respond_to_challenge); | 
 |  | 
 | /* | 
 |  * Parse sendmsg() control message and respond to challenge.  We need to see if | 
 |  * there's an appdata to fish out. | 
 |  */ | 
 | static int rxgk_sendmsg_respond_to_challenge(struct sk_buff *challenge, | 
 | 					     struct msghdr *msg) | 
 | { | 
 | 	struct krb5_buffer appdata = {}; | 
 | 	struct cmsghdr *cmsg; | 
 |  | 
 | 	for_each_cmsghdr(cmsg, msg) { | 
 | 		if (cmsg->cmsg_level != SOL_RXRPC || | 
 | 		    cmsg->cmsg_type != RXRPC_RESP_RXGK_APPDATA) | 
 | 			continue; | 
 | 		if (appdata.data) | 
 | 			return -EINVAL; | 
 | 		appdata.data = CMSG_DATA(cmsg); | 
 | 		appdata.len = cmsg->cmsg_len - sizeof(struct cmsghdr); | 
 | 	} | 
 |  | 
 | 	return rxgk_kernel_respond_to_challenge(challenge, &appdata); | 
 | } | 
 |  | 
 | /* | 
 |  * Verify the authenticator. | 
 |  * | 
 |  * struct RXGK_Authenticator { | 
 |  *	opaque nonce[20]; | 
 |  *	opaque appdata<>; | 
 |  *	RXGK_Level level; | 
 |  *	unsigned int epoch; | 
 |  *	unsigned int cid; | 
 |  *	unsigned int call_numbers<>; | 
 |  * }; | 
 |  */ | 
 | static int rxgk_do_verify_authenticator(struct rxrpc_connection *conn, | 
 | 					const struct krb5_enctype *krb5, | 
 | 					struct sk_buff *skb, | 
 | 					__be32 *p, __be32 *end) | 
 | { | 
 | 	u32 app_len, call_count, level, epoch, cid, i; | 
 |  | 
 | 	_enter(""); | 
 |  | 
 | 	if (memcmp(p, conn->rxgk.nonce, 20) != 0) | 
 | 		return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO, | 
 | 					rxgk_abort_resp_bad_nonce); | 
 | 	p += 20 / sizeof(__be32); | 
 |  | 
 | 	app_len	= ntohl(*p++); | 
 | 	if (app_len > (end - p) * sizeof(__be32)) | 
 | 		return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO, | 
 | 					rxgk_abort_resp_short_applen); | 
 |  | 
 | 	p += xdr_round_up(app_len) / sizeof(__be32); | 
 | 	if (end - p < 4) | 
 | 		return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO, | 
 | 					rxgk_abort_resp_short_applen); | 
 |  | 
 | 	level	= ntohl(*p++); | 
 | 	epoch	= ntohl(*p++); | 
 | 	cid	= ntohl(*p++); | 
 | 	call_count = ntohl(*p++); | 
 |  | 
 | 	if (level	!= conn->security_level || | 
 | 	    epoch	!= conn->proto.epoch || | 
 | 	    cid		!= conn->proto.cid || | 
 | 	    call_count	> 4) | 
 | 		return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO, | 
 | 					rxgk_abort_resp_bad_param); | 
 |  | 
 | 	if (end - p < call_count) | 
 | 		return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO, | 
 | 					rxgk_abort_resp_short_call_list); | 
 |  | 
 | 	for (i = 0; i < call_count; i++) { | 
 | 		u32 call_id = ntohl(*p++); | 
 |  | 
 | 		if (call_id > INT_MAX) | 
 | 			return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO, | 
 | 						rxgk_abort_resp_bad_callid); | 
 |  | 
 | 		if (call_id < conn->channels[i].call_counter) | 
 | 			return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO, | 
 | 						rxgk_abort_resp_call_ctr); | 
 |  | 
 | 		if (call_id > conn->channels[i].call_counter) { | 
 | 			if (conn->channels[i].call) | 
 | 				return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO, | 
 | 							rxgk_abort_resp_call_state); | 
 |  | 
 | 			conn->channels[i].call_counter = call_id; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	_leave(" = 0"); | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  * Extract the authenticator and verify it. | 
 |  */ | 
 | static int rxgk_verify_authenticator(struct rxrpc_connection *conn, | 
 | 				     const struct krb5_enctype *krb5, | 
 | 				     struct sk_buff *skb, | 
 | 				     unsigned int auth_offset, unsigned int auth_len) | 
 | { | 
 | 	void *auth; | 
 | 	__be32 *p; | 
 | 	int ret; | 
 |  | 
 | 	auth = kmalloc(auth_len, GFP_NOFS); | 
 | 	if (!auth) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	ret = skb_copy_bits(skb, auth_offset, auth, auth_len); | 
 | 	if (ret < 0) { | 
 | 		ret = rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO, | 
 | 				       rxgk_abort_resp_short_auth); | 
 | 		goto error; | 
 | 	} | 
 |  | 
 | 	p = auth; | 
 | 	ret = rxgk_do_verify_authenticator(conn, krb5, skb, p, p + auth_len); | 
 | error: | 
 | 	kfree(auth); | 
 | 	return ret; | 
 | } | 
 |  | 
 | /* | 
 |  * Verify a response. | 
 |  * | 
 |  * struct RXGK_Response { | 
 |  *	rxgkTime	start_time; | 
 |  *	RXGK_Data	token; | 
 |  *	opaque		authenticator<RXGK_MAXAUTHENTICATOR> | 
 |  * }; | 
 |  */ | 
 | static int rxgk_verify_response(struct rxrpc_connection *conn, | 
 | 				struct sk_buff *skb) | 
 | { | 
 | 	const struct krb5_enctype *krb5; | 
 | 	struct rxrpc_key_token *token; | 
 | 	struct rxrpc_skb_priv *sp = rxrpc_skb(skb); | 
 | 	struct rxgk_response rhdr; | 
 | 	struct rxgk_context *gk; | 
 | 	struct key *key = NULL; | 
 | 	unsigned int offset = sizeof(struct rxrpc_wire_header); | 
 | 	unsigned int len = skb->len - sizeof(struct rxrpc_wire_header); | 
 | 	unsigned int token_offset, token_len; | 
 | 	unsigned int auth_offset, auth_len; | 
 | 	__be32 xauth_len; | 
 | 	int ret, ec; | 
 |  | 
 | 	_enter("{%d}", conn->debug_id); | 
 |  | 
 | 	/* Parse the RXGK_Response object */ | 
 | 	if (sizeof(rhdr) + sizeof(__be32) > len) | 
 | 		goto short_packet; | 
 |  | 
 | 	if (skb_copy_bits(skb, offset, &rhdr, sizeof(rhdr)) < 0) | 
 | 		goto short_packet; | 
 | 	offset	+= sizeof(rhdr); | 
 | 	len	-= sizeof(rhdr); | 
 |  | 
 | 	token_offset	= offset; | 
 | 	token_len	= ntohl(rhdr.token_len); | 
 | 	if (xdr_round_up(token_len) + sizeof(__be32) > len) | 
 | 		goto short_packet; | 
 |  | 
 | 	trace_rxrpc_rx_response(conn, sp->hdr.serial, 0, sp->hdr.cksum, token_len); | 
 |  | 
 | 	offset	+= xdr_round_up(token_len); | 
 | 	len	-= xdr_round_up(token_len); | 
 |  | 
 | 	if (skb_copy_bits(skb, offset, &xauth_len, sizeof(xauth_len)) < 0) | 
 | 		goto short_packet; | 
 | 	offset	+= sizeof(xauth_len); | 
 | 	len	-= sizeof(xauth_len); | 
 |  | 
 | 	auth_offset	= offset; | 
 | 	auth_len	= ntohl(xauth_len); | 
 | 	if (auth_len < len) | 
 | 		goto short_packet; | 
 | 	if (auth_len & 3) | 
 | 		goto inconsistent; | 
 | 	if (auth_len < 20 + 9 * 4) | 
 | 		goto auth_too_short; | 
 |  | 
 | 	/* We need to extract and decrypt the token and instantiate a session | 
 | 	 * key for it.  This bit, however, is application-specific.  If | 
 | 	 * possible, we use a default parser, but we might end up bumping this | 
 | 	 * to the app to deal with - which might mean a round trip to | 
 | 	 * userspace. | 
 | 	 */ | 
 | 	ret = rxgk_extract_token(conn, skb, token_offset, token_len, &key); | 
 | 	if (ret < 0) | 
 | 		goto out; | 
 |  | 
 | 	/* We now have a key instantiated from the decrypted ticket.  We can | 
 | 	 * pass this to the application so that they can parse the ticket | 
 | 	 * content and we can use the session key it contains to derive the | 
 | 	 * keys we need. | 
 | 	 * | 
 | 	 * Note that we have to switch enctype at this point as the enctype of | 
 | 	 * the ticket doesn't necessarily match that of the transport. | 
 | 	 */ | 
 | 	token = key->payload.data[0]; | 
 | 	conn->security_level = token->rxgk->level; | 
 | 	conn->rxgk.start_time = __be64_to_cpu(rhdr.start_time); | 
 |  | 
 | 	gk = rxgk_generate_transport_key(conn, token->rxgk, sp->hdr.cksum, GFP_NOFS); | 
 | 	if (IS_ERR(gk)) { | 
 | 		ret = PTR_ERR(gk); | 
 | 		goto cant_get_token; | 
 | 	} | 
 |  | 
 | 	krb5 = gk->krb5; | 
 |  | 
 | 	trace_rxrpc_rx_response(conn, sp->hdr.serial, krb5->etype, sp->hdr.cksum, token_len); | 
 |  | 
 | 	/* Decrypt, parse and verify the authenticator. */ | 
 | 	ret = rxgk_decrypt_skb(krb5, gk->resp_enc, skb, | 
 | 			       &auth_offset, &auth_len, &ec); | 
 | 	if (ret < 0) { | 
 | 		rxrpc_abort_conn(conn, skb, RXGK_SEALEDINCON, ret, | 
 | 				 rxgk_abort_resp_auth_dec); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	ret = rxgk_verify_authenticator(conn, krb5, skb, auth_offset, auth_len); | 
 | 	if (ret < 0) | 
 | 		goto out; | 
 |  | 
 | 	conn->key = key; | 
 | 	key = NULL; | 
 | 	ret = 0; | 
 | out: | 
 | 	key_put(key); | 
 | 	_leave(" = %d", ret); | 
 | 	return ret; | 
 |  | 
 | inconsistent: | 
 | 	ret = rxrpc_abort_conn(conn, skb, RXGK_INCONSISTENCY, -EPROTO, | 
 | 			       rxgk_abort_resp_xdr_align); | 
 | 	goto out; | 
 | auth_too_short: | 
 | 	ret = rxrpc_abort_conn(conn, skb, RXGK_PACKETSHORT, -EPROTO, | 
 | 			       rxgk_abort_resp_short_auth); | 
 | 	goto out; | 
 | short_packet: | 
 | 	ret = rxrpc_abort_conn(conn, skb, RXGK_PACKETSHORT, -EPROTO, | 
 | 			       rxgk_abort_resp_short_packet); | 
 | 	goto out; | 
 |  | 
 | cant_get_token: | 
 | 	switch (ret) { | 
 | 	case -ENOMEM: | 
 | 		goto temporary_error; | 
 | 	case -EINVAL: | 
 | 		ret = rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EKEYREJECTED, | 
 | 				       rxgk_abort_resp_internal_error); | 
 | 		goto out; | 
 | 	case -ENOPKG: | 
 | 		ret = rxrpc_abort_conn(conn, skb, KRB5_PROG_KEYTYPE_NOSUPP, | 
 | 				       -EKEYREJECTED, rxgk_abort_resp_nopkg); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | temporary_error: | 
 | 	/* Ignore the response packet if we got a temporary error such as | 
 | 	 * ENOMEM.  We just want to send the challenge again.  Note that we | 
 | 	 * also come out this way if the ticket decryption fails. | 
 | 	 */ | 
 | 	goto out; | 
 | } | 
 |  | 
 | /* | 
 |  * clear the connection security | 
 |  */ | 
 | static void rxgk_clear(struct rxrpc_connection *conn) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < ARRAY_SIZE(conn->rxgk.keys); i++) | 
 | 		rxgk_put(conn->rxgk.keys[i]); | 
 | } | 
 |  | 
 | /* | 
 |  * Initialise the RxGK security service. | 
 |  */ | 
 | static int rxgk_init(void) | 
 | { | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  * Clean up the RxGK security service. | 
 |  */ | 
 | static void rxgk_exit(void) | 
 | { | 
 | } | 
 |  | 
 | /* | 
 |  * RxRPC YFS GSSAPI-based security | 
 |  */ | 
 | const struct rxrpc_security rxgk_yfs = { | 
 | 	.name				= "yfs-rxgk", | 
 | 	.security_index			= RXRPC_SECURITY_YFS_RXGK, | 
 | 	.no_key_abort			= RXGK_NOTAUTH, | 
 | 	.init				= rxgk_init, | 
 | 	.exit				= rxgk_exit, | 
 | 	.preparse_server_key		= rxgk_preparse_server_key, | 
 | 	.free_preparse_server_key	= rxgk_free_preparse_server_key, | 
 | 	.destroy_server_key		= rxgk_destroy_server_key, | 
 | 	.describe_server_key		= rxgk_describe_server_key, | 
 | 	.init_connection_security	= rxgk_init_connection_security, | 
 | 	.alloc_txbuf			= rxgk_alloc_txbuf, | 
 | 	.secure_packet			= rxgk_secure_packet, | 
 | 	.verify_packet			= rxgk_verify_packet, | 
 | 	.free_call_crypto		= rxgk_free_call_crypto, | 
 | 	.issue_challenge		= rxgk_issue_challenge, | 
 | 	.validate_challenge		= rxgk_validate_challenge, | 
 | 	.challenge_to_recvmsg		= rxgk_challenge_to_recvmsg, | 
 | 	.sendmsg_respond_to_challenge	= rxgk_sendmsg_respond_to_challenge, | 
 | 	.respond_to_challenge		= rxgk_respond_to_challenge_no_appdata, | 
 | 	.verify_response		= rxgk_verify_response, | 
 | 	.clear				= rxgk_clear, | 
 | 	.default_decode_ticket		= rxgk_yfs_decode_ticket, | 
 | }; |