Commit 34f61a07 authored by David Howells's avatar David Howells Committed by Jakub Kicinski
Browse files

rxrpc: Fix memory leaks in rxkad_verify_response()

Fix rxkad_verify_response() to free the ticket and the server key under all
circumstances by initialising the ticket pointer to NULL and then making
all paths through the function after the first allocation has been done go
through a single common epilogue that just releases everything - where all
the releases skip on a NULL pointer.

Fixes: 57af281e ("rxrpc: Tidy up abort generation infrastructure")
Fixes: ec832bd0 ("rxrpc: Don't retain the server key in the connection")
Closes: https://sashiko.dev/#/patchset/20260408121252.2249051-1-dhowells%40redhat.com


Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: Jeffrey Altman <jaltman@auristor.com>
cc: Simon Horman <horms@kernel.org>
cc: linux-afs@lists.infradead.org
cc: stable@kernel.org
Link: https://patch.msgid.link/20260422161438.2593376-2-dhowells@redhat.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 8141a2dc
Loading
Loading
Loading
Loading
+42 −61
Original line number Diff line number Diff line
@@ -1136,7 +1136,7 @@ static int rxkad_verify_response(struct rxrpc_connection *conn,
	struct rxrpc_crypt session_key;
	struct key *server_key;
	time64_t expiry;
	void *ticket;
	void *ticket = NULL;
	u32 version, kvno, ticket_len, level;
	__be32 csum;
	int ret, i;
@@ -1162,13 +1162,13 @@ static int rxkad_verify_response(struct rxrpc_connection *conn,
	ret = -ENOMEM;
	response = kzalloc_obj(struct rxkad_response, GFP_NOFS);
	if (!response)
		goto temporary_error;
		goto error;

	if (skb_copy_bits(skb, sizeof(struct rxrpc_wire_header),
			  response, sizeof(*response)) < 0) {
		rxrpc_abort_conn(conn, skb, RXKADPACKETSHORT, -EPROTO,
		ret = rxrpc_abort_conn(conn, skb, RXKADPACKETSHORT, -EPROTO,
				       rxkad_abort_resp_short);
		goto protocol_error;
		goto error;
	}

	version = ntohl(response->version);
@@ -1178,62 +1178,62 @@ static int rxkad_verify_response(struct rxrpc_connection *conn,
	trace_rxrpc_rx_response(conn, sp->hdr.serial, version, kvno, ticket_len);

	if (version != RXKAD_VERSION) {
		rxrpc_abort_conn(conn, skb, RXKADINCONSISTENCY, -EPROTO,
		ret = rxrpc_abort_conn(conn, skb, RXKADINCONSISTENCY, -EPROTO,
				       rxkad_abort_resp_version);
		goto protocol_error;
		goto error;
	}

	if (ticket_len < 4 || ticket_len > MAXKRB5TICKETLEN) {
		rxrpc_abort_conn(conn, skb, RXKADTICKETLEN, -EPROTO,
		ret = rxrpc_abort_conn(conn, skb, RXKADTICKETLEN, -EPROTO,
				       rxkad_abort_resp_tkt_len);
		goto protocol_error;
		goto error;
	}

	if (kvno >= RXKAD_TKT_TYPE_KERBEROS_V5) {
		rxrpc_abort_conn(conn, skb, RXKADUNKNOWNKEY, -EPROTO,
		ret = rxrpc_abort_conn(conn, skb, RXKADUNKNOWNKEY, -EPROTO,
				       rxkad_abort_resp_unknown_tkt);
		goto protocol_error;
		goto error;
	}

	/* extract the kerberos ticket and decrypt and decode it */
	ret = -ENOMEM;
	ticket = kmalloc(ticket_len, GFP_NOFS);
	if (!ticket)
		goto temporary_error_free_resp;
		goto error;

	if (skb_copy_bits(skb, sizeof(struct rxrpc_wire_header) + sizeof(*response),
			  ticket, ticket_len) < 0) {
		rxrpc_abort_conn(conn, skb, RXKADPACKETSHORT, -EPROTO,
		ret = rxrpc_abort_conn(conn, skb, RXKADPACKETSHORT, -EPROTO,
				       rxkad_abort_resp_short_tkt);
		goto protocol_error;
		goto error;
	}

	ret = rxkad_decrypt_ticket(conn, server_key, skb, ticket, ticket_len,
				   &session_key, &expiry);
	if (ret < 0)
		goto temporary_error_free_ticket;
		goto error;

	/* use the session key from inside the ticket to decrypt the
	 * response */
	ret = rxkad_decrypt_response(conn, response, &session_key);
	if (ret < 0)
		goto temporary_error_free_ticket;
		goto error;

	if (ntohl(response->encrypted.epoch) != conn->proto.epoch ||
	    ntohl(response->encrypted.cid) != conn->proto.cid ||
	    ntohl(response->encrypted.securityIndex) != conn->security_ix) {
		rxrpc_abort_conn(conn, skb, RXKADSEALEDINCON, -EPROTO,
		ret = rxrpc_abort_conn(conn, skb, RXKADSEALEDINCON, -EPROTO,
				       rxkad_abort_resp_bad_param);
		goto protocol_error_free;
		goto error;
	}

	csum = response->encrypted.checksum;
	response->encrypted.checksum = 0;
	rxkad_calc_response_checksum(response);
	if (response->encrypted.checksum != csum) {
		rxrpc_abort_conn(conn, skb, RXKADSEALEDINCON, -EPROTO,
		ret = rxrpc_abort_conn(conn, skb, RXKADSEALEDINCON, -EPROTO,
				       rxkad_abort_resp_bad_checksum);
		goto protocol_error_free;
		goto error;
	}

	for (i = 0; i < RXRPC_MAXCALLS; i++) {
@@ -1241,38 +1241,38 @@ static int rxkad_verify_response(struct rxrpc_connection *conn,
		u32 counter = READ_ONCE(conn->channels[i].call_counter);

		if (call_id > INT_MAX) {
			rxrpc_abort_conn(conn, skb, RXKADSEALEDINCON, -EPROTO,
			ret = rxrpc_abort_conn(conn, skb, RXKADSEALEDINCON, -EPROTO,
					       rxkad_abort_resp_bad_callid);
			goto protocol_error_free;
			goto error;
		}

		if (call_id < counter) {
			rxrpc_abort_conn(conn, skb, RXKADSEALEDINCON, -EPROTO,
			ret = rxrpc_abort_conn(conn, skb, RXKADSEALEDINCON, -EPROTO,
					       rxkad_abort_resp_call_ctr);
			goto protocol_error_free;
			goto error;
		}

		if (call_id > counter) {
			if (conn->channels[i].call) {
				rxrpc_abort_conn(conn, skb, RXKADSEALEDINCON, -EPROTO,
				ret = rxrpc_abort_conn(conn, skb, RXKADSEALEDINCON, -EPROTO,
						 rxkad_abort_resp_call_state);
				goto protocol_error_free;
				goto error;
			}
			conn->channels[i].call_counter = call_id;
		}
	}

	if (ntohl(response->encrypted.inc_nonce) != conn->rxkad.nonce + 1) {
		rxrpc_abort_conn(conn, skb, RXKADOUTOFSEQUENCE, -EPROTO,
		ret = rxrpc_abort_conn(conn, skb, RXKADOUTOFSEQUENCE, -EPROTO,
				       rxkad_abort_resp_ooseq);
		goto protocol_error_free;
		goto error;
	}

	level = ntohl(response->encrypted.level);
	if (level > RXRPC_SECURITY_ENCRYPT) {
		rxrpc_abort_conn(conn, skb, RXKADLEVELFAIL, -EPROTO,
		ret = rxrpc_abort_conn(conn, skb, RXKADLEVELFAIL, -EPROTO,
				       rxkad_abort_resp_level);
		goto protocol_error_free;
		goto error;
	}
	conn->security_level = level;

@@ -1280,31 +1280,12 @@ static int rxkad_verify_response(struct rxrpc_connection *conn,
	 * this the connection security can be handled in exactly the same way
	 * as for a client connection */
	ret = rxrpc_get_server_data_key(conn, &session_key, expiry, kvno);
	if (ret < 0)
		goto temporary_error_free_ticket;

	kfree(ticket);
	kfree(response);
	_leave(" = 0");
	return 0;

protocol_error_free:
	kfree(ticket);
protocol_error:
	kfree(response);
	key_put(server_key);
	return -EPROTO;

temporary_error_free_ticket:
error:
	kfree(ticket);
temporary_error_free_resp:
	kfree(response);
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.
	 */
	key_put(server_key);
	_leave(" = %d", ret);
	return ret;
}