Merge branch 'rxrpc-better-fix-for-data-response-decrypt-vs-splice'

David Howells says:

====================
rxrpc: Better fix for DATA/RESPONSE decrypt vs splice()

Here are two patches containing better fixes for the in-place decryption of
DATA and RESPONSE packets that can corrupt pagecache spliced into UDP
packets and sent to an AF_RXRPC server [CVE-2026-43500], plus a patch to
precheck the length of rxgk-secured DATA packets.

Of the main patches, one patch fixes DATA decryption by having recvmsg
unconditionally extract the data into a flat bounce buffer and, if need be,
decrypt it there.  It doesn't seem to cause a performance problem to do
this even on unencrypted packets; for encrypted packets it makes sure the
content is correctly aligned for crypto which seems to get a small
performance gain.

Further, it means that DATA packets are no longer copied in the I/O thread,
avoiding a slowdown of the protocol engine that runs there.

The other main patch fixes RESPONSE decryption by having the connection
event handler worker copy the data to a flat buffer and, again, decrypt it
there.  This simplifies RESPONSE handling.

With these two fixes, the data content of the received sk_buff no longer
gets altered.
====================

Link: https://patch.msgid.link/20260515230516.2718212-1-dhowells@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-05-20 16:36:47 -07:00
commit 7769d17e02
14 changed files with 308 additions and 304 deletions

View File

@ -158,13 +158,22 @@ returned.
When a message has been received, the location and size of the data with the
message can be determined by calling::
void crypto_krb5_where_is_the_data(const struct krb5_enctype *krb5,
enum krb5_crypto_mode mode,
size_t *_offset, size_t *_len);
int crypto_krb5_where_is_the_data(const struct krb5_enctype *krb5,
enum krb5_crypto_mode mode,
size_t *_offset, size_t *_len);
The caller provides the offset and length of the message to the function, which
then alters those values to indicate the region containing the data (plus any
padding). It is up to the caller to determine how much padding there is.
padding). It is up to the caller to determine how much padding there is. The
function returns an error if the length is too small or if the mode is
unsupported. An additional function::
int crypto_krb5_check_data_len(const struct krb5_enctype *krb5,
enum krb5_crypto_mode mode,
size_t len, size_t min_content);
is provided to just do a basic check that the decrypted/verified message would
have a sufficient minimum payload.
Preparation Functions
---------------------

View File

@ -134,27 +134,69 @@ EXPORT_SYMBOL(crypto_krb5_how_much_data);
* Find the offset and size of the data in a secure message so that this
* information can be used in the metadata buffer which will get added to the
* digest by crypto_krb5_verify_mic().
*
* Return: 0 if successful, -EBADMSG if the message is too short or -EINVAL if
* the mode is unsupported.
*/
void crypto_krb5_where_is_the_data(const struct krb5_enctype *krb5,
enum krb5_crypto_mode mode,
size_t *_offset, size_t *_len)
int crypto_krb5_where_is_the_data(const struct krb5_enctype *krb5,
enum krb5_crypto_mode mode,
size_t *_offset, size_t *_len)
{
switch (mode) {
case KRB5_CHECKSUM_MODE:
if (*_len < krb5->cksum_len)
return -EBADMSG;
*_offset += krb5->cksum_len;
*_len -= krb5->cksum_len;
return;
return 0;
case KRB5_ENCRYPT_MODE:
if (*_len < krb5->conf_len + krb5->cksum_len)
return -EBADMSG;
*_offset += krb5->conf_len;
*_len -= krb5->conf_len + krb5->cksum_len;
return;
return 0;
default:
WARN_ON_ONCE(1);
return;
return -EINVAL;
}
}
EXPORT_SYMBOL(crypto_krb5_where_is_the_data);
/**
* crypto_krb5_check_data_len - Check a message is big enough
* @krb5: The encoding to use.
* @mode: Mode of operation.
* @len: The length of the secure blob.
* @min_content: Minimum length of the content inside the blob.
*
* Check that a message is large enough to hold whatever bits the encryption
* type wants to glue on (nonce, checksum) plus a minimum amount of content.
*
* Return: 0 if successful, -EBADMSG if the message is too short or -EINVAL if
* the mode is unsupported.
*/
int crypto_krb5_check_data_len(const struct krb5_enctype *krb5,
enum krb5_crypto_mode mode,
size_t len, size_t min_content)
{
switch (mode) {
case KRB5_CHECKSUM_MODE:
if (len < krb5->cksum_len ||
len - krb5->cksum_len < min_content)
return -EBADMSG;
return 0;
case KRB5_ENCRYPT_MODE:
if (len < krb5->conf_len + krb5->cksum_len ||
len - (krb5->conf_len + krb5->cksum_len) < min_content)
return -EBADMSG;
return 0;
default:
WARN_ON_ONCE(1);
return -EINVAL;
}
}
EXPORT_SYMBOL(crypto_krb5_check_data_len);
/*
* Prepare the encryption with derived key data.
*/

View File

@ -121,9 +121,12 @@ size_t crypto_krb5_how_much_buffer(const struct krb5_enctype *krb5,
size_t crypto_krb5_how_much_data(const struct krb5_enctype *krb5,
enum krb5_crypto_mode mode,
size_t *_buffer_size, size_t *_offset);
void crypto_krb5_where_is_the_data(const struct krb5_enctype *krb5,
enum krb5_crypto_mode mode,
size_t *_offset, size_t *_len);
int crypto_krb5_where_is_the_data(const struct krb5_enctype *krb5,
enum krb5_crypto_mode mode,
size_t *_offset, size_t *_len);
int crypto_krb5_check_data_len(const struct krb5_enctype *krb5,
enum krb5_crypto_mode mode,
size_t len, size_t min_content);
struct crypto_aead *crypto_krb5_prepare_encryption(const struct krb5_enctype *krb5,
const struct krb5_buffer *TK,
u32 usage, gfp_t gfp);

View File

@ -71,6 +71,7 @@
EM(rxkad_abort_resp_unknown_tkt, "rxkad-resp-unknown-tkt") \
EM(rxkad_abort_resp_version, "rxkad-resp-version") \
/* RxGK security errors */ \
EM(rxgk_abort_1_short_header, "rxgk1-short-hdr") \
EM(rxgk_abort_1_verify_mic_eproto, "rxgk1-vfy-mic-eproto") \
EM(rxgk_abort_2_decrypt_eproto, "rxgk2-dec-eproto") \
EM(rxgk_abort_2_short_data, "rxgk2-short-data") \

View File

@ -213,8 +213,6 @@ struct rxrpc_skb_priv {
struct {
u16 offset; /* Offset of data */
u16 len; /* Length of data */
u8 flags;
#define RXRPC_RX_VERIFIED 0x01
};
struct {
rxrpc_seq_t first_ack; /* First packet in acks table */
@ -309,15 +307,16 @@ struct rxrpc_security {
struct sk_buff *challenge);
/* verify a response */
int (*verify_response)(struct rxrpc_connection *,
struct sk_buff *);
int (*verify_response)(struct rxrpc_connection *conn,
struct sk_buff *response_skb,
void *response, unsigned int len);
/* clear connection security */
void (*clear)(struct rxrpc_connection *);
/* Default ticket -> key decoder */
int (*default_decode_ticket)(struct rxrpc_connection *conn, struct sk_buff *skb,
unsigned int ticket_offset, unsigned int ticket_len,
void *ticket, unsigned int ticket_len,
struct key **_key);
};
@ -774,6 +773,11 @@ struct rxrpc_call {
struct sk_buff_head recvmsg_queue; /* Queue of packets ready for recvmsg() */
struct sk_buff_head rx_queue; /* Queue of packets for this call to receive */
struct sk_buff_head rx_oos_queue; /* Queue of out of sequence packets */
void *rx_dec_buffer; /* Decryption buffer */
unsigned short rx_dec_bsize; /* rx_dec_buffer size */
unsigned short rx_dec_offset; /* Decrypted packet data offset */
unsigned short rx_dec_len; /* Decrypted packet data len */
rxrpc_seq_t rx_dec_seq; /* Packet in decryption buffer */
rxrpc_seq_t rx_highest_seq; /* Higest sequence number received */
rxrpc_seq_t rx_consumed; /* Highest packet consumed */

View File

@ -332,27 +332,7 @@ bool rxrpc_input_call_event(struct rxrpc_call *call)
saw_ack |= sp->hdr.type == RXRPC_PACKET_TYPE_ACK;
if (sp->hdr.type == RXRPC_PACKET_TYPE_DATA &&
sp->hdr.securityIndex != 0 &&
(skb_cloned(skb) ||
skb_has_frag_list(skb) ||
skb_has_shared_frag(skb))) {
/* Unshare the packet so that it can be
* modified by in-place decryption.
*/
struct sk_buff *nskb = skb_copy(skb, GFP_ATOMIC);
if (nskb) {
rxrpc_new_skb(nskb, rxrpc_skb_new_unshared);
rxrpc_input_call_packet(call, nskb);
rxrpc_free_skb(nskb, rxrpc_skb_put_call_rx);
} else {
/* OOM - Drop the packet. */
rxrpc_see_skb(skb, rxrpc_skb_see_unshare_nomem);
}
} else {
rxrpc_input_call_packet(call, skb);
}
rxrpc_input_call_packet(call, skb);
rxrpc_free_skb(skb, rxrpc_skb_put_call_rx);
did_receive = true;
}

View File

@ -152,6 +152,7 @@ struct rxrpc_call *rxrpc_alloc_call(struct rxrpc_sock *rx, gfp_t gfp,
spin_lock_init(&call->notify_lock);
refcount_set(&call->ref, 1);
call->debug_id = debug_id;
call->rx_pkt_offset = USHRT_MAX;
call->tx_total_len = -1;
call->tx_jumbo_max = 1;
call->next_rx_timo = 20 * HZ;
@ -553,6 +554,7 @@ static void rxrpc_cleanup_rx_buffers(struct rxrpc_call *call)
rxrpc_purge_queue(&call->recvmsg_queue);
rxrpc_purge_queue(&call->rx_queue);
rxrpc_purge_queue(&call->rx_oos_queue);
kfree(call->rx_dec_buffer);
}
/*

View File

@ -243,28 +243,22 @@ static void rxrpc_call_is_secure(struct rxrpc_call *call)
static int rxrpc_verify_response(struct rxrpc_connection *conn,
struct sk_buff *skb)
{
unsigned int len = skb->len - sizeof(struct rxrpc_wire_header);
void *buffer;
int ret;
if (skb_cloned(skb) || skb_has_frag_list(skb) ||
skb_has_shared_frag(skb)) {
/* Copy the packet if shared so that we can do in-place
* decryption.
*/
struct sk_buff *nskb = skb_copy(skb, GFP_NOFS);
buffer = kmalloc(len, GFP_NOFS);
if (!buffer)
return -ENOMEM;
if (nskb) {
rxrpc_new_skb(nskb, rxrpc_skb_new_unshared);
ret = conn->security->verify_response(conn, nskb);
rxrpc_free_skb(nskb, rxrpc_skb_put_response_copy);
} else {
/* OOM - Drop the packet. */
rxrpc_see_skb(skb, rxrpc_skb_see_unshare_nomem);
ret = -ENOMEM;
}
} else {
ret = conn->security->verify_response(conn, skb);
}
ret = skb_copy_bits(skb, sizeof(struct rxrpc_wire_header), buffer, len);
if (ret < 0)
goto out;
ret = conn->security->verify_response(conn, skb, buffer, len);
out:
kfree(buffer);
return ret;
}

View File

@ -32,9 +32,6 @@ static int none_secure_packet(struct rxrpc_call *call, struct rxrpc_txbuf *txb)
static int none_verify_packet(struct rxrpc_call *call, struct sk_buff *skb)
{
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
sp->flags |= RXRPC_RX_VERIFIED;
return 0;
}
@ -57,9 +54,10 @@ static int none_sendmsg_respond_to_challenge(struct sk_buff *challenge,
}
static int none_verify_response(struct rxrpc_connection *conn,
struct sk_buff *skb)
struct sk_buff *response_skb,
void *response, unsigned int len)
{
return rxrpc_abort_conn(conn, skb, RX_PROTOCOL_ERROR, -EPROTO,
return rxrpc_abort_conn(conn, response_skb, RX_PROTOCOL_ERROR, -EPROTO,
rxrpc_eproto_rxnull_response);
}

View File

@ -147,15 +147,52 @@ static void rxrpc_rotate_rx_window(struct rxrpc_call *call)
}
/*
* Decrypt and verify a DATA packet.
* Decrypt and verify a DATA packet. The content of the packet is pulled out
* into a flat buffer rather than decrypting in place in the skbuff. This also
* has the advantage of aligning the buffer correctly for the crypto routines.
*
* We keep track of the sequence number of the packet currently decrypted into
* the buffer in ->rx_dec_seq. If MSG_PEEK is used and steps onto a new
* packet, subsequent recvmsg() calls will have to go back and re-decrypt the
* current packet.
*/
static int rxrpc_verify_data(struct rxrpc_call *call, struct sk_buff *skb)
{
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
int ret;
if (sp->flags & RXRPC_RX_VERIFIED)
return 0;
return call->security->verify_packet(call, skb);
if (sp->len > call->rx_dec_bsize) {
/* Make sure we can hold a 1412-byte jumbo subpacket and make
* sure that the buffer size is aligned to a crypto blocksize.
*/
size_t size = clamp(round_up(sp->len, 32), 2048, 65535);
void *buffer = krealloc(call->rx_dec_buffer, size, GFP_NOFS);
if (!buffer)
return -ENOMEM;
call->rx_dec_buffer = buffer;
call->rx_dec_bsize = size;
}
ret = -EFAULT;
if (skb_copy_bits(skb, sp->offset, call->rx_dec_buffer, sp->len) < 0)
goto err;
call->rx_dec_offset = 0;
call->rx_dec_len = sp->len;
call->rx_dec_seq = sp->hdr.seq;
ret = call->security->verify_packet(call, skb);
if (ret < 0)
goto err;
return 0;
err:
kfree(call->rx_dec_buffer);
call->rx_dec_buffer = NULL;
call->rx_dec_bsize = 0;
call->rx_dec_offset = 0;
call->rx_dec_len = 0;
return ret;
}
/*
@ -283,16 +320,21 @@ static int rxrpc_recvmsg_data(struct socket *sock, struct rxrpc_call *call,
if (msg)
sock_recv_timestamp(msg, sock->sk, skb);
if (rx_pkt_offset == 0) {
if (call->rx_dec_seq != sp->hdr.seq ||
!call->rx_dec_buffer) {
ret2 = rxrpc_verify_data(call, skb);
trace_rxrpc_recvdata(call, rxrpc_recvmsg_next, seq,
sp->offset, sp->len, ret2);
call->rx_dec_offset,
call->rx_dec_len, ret2);
if (ret2 < 0) {
ret = ret2;
goto out;
}
rx_pkt_offset = sp->offset;
rx_pkt_len = sp->len;
}
if (rx_pkt_offset == USHRT_MAX) {
rx_pkt_offset = call->rx_dec_offset;
rx_pkt_len = call->rx_dec_len;
} else {
trace_rxrpc_recvdata(call, rxrpc_recvmsg_cont, seq,
rx_pkt_offset, rx_pkt_len, 0);
@ -304,10 +346,10 @@ static int rxrpc_recvmsg_data(struct socket *sock, struct rxrpc_call *call,
if (copy > remain)
copy = remain;
if (copy > 0) {
ret2 = skb_copy_datagram_iter(skb, rx_pkt_offset, iter,
copy);
if (ret2 < 0) {
ret = ret2;
ret2 = copy_to_iter(call->rx_dec_buffer + rx_pkt_offset,
copy, iter);
if (ret2 != copy) {
ret = -EFAULT;
goto out;
}
@ -328,7 +370,7 @@ static int rxrpc_recvmsg_data(struct socket *sock, struct rxrpc_call *call,
/* The whole packet has been transferred. */
if (sp->hdr.flags & RXRPC_LAST_PACKET)
ret = 1;
rx_pkt_offset = 0;
rx_pkt_offset = USHRT_MAX;
rx_pkt_len = 0;
skb = skb_peek_next(skb, &call->recvmsg_queue);

View File

@ -473,15 +473,20 @@ static int rxgk_verify_packet_integrity(struct rxrpc_call *call,
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
struct rxgk_header *hdr;
struct krb5_buffer metadata;
unsigned int offset = sp->offset, len = sp->len;
unsigned int len = call->rx_dec_len;
size_t data_offset = 0, data_len = len;
void *data = call->rx_dec_buffer, *p = data;
u32 ac = 0;
int ret = -ENOMEM;
_enter("");
crypto_krb5_where_is_the_data(gk->krb5, KRB5_CHECKSUM_MODE,
&data_offset, &data_len);
if (crypto_krb5_where_is_the_data(gk->krb5, KRB5_CHECKSUM_MODE,
&data_offset, &data_len) < 0) {
ret = rxrpc_abort_eproto(call, skb, RXGK_PACKETSHORT,
rxgk_abort_1_short_header);
goto put_gk;
}
hdr = kzalloc_obj(*hdr, GFP_NOFS);
if (!hdr)
@ -496,16 +501,15 @@ static int rxgk_verify_packet_integrity(struct rxrpc_call *call,
metadata.len = sizeof(*hdr);
metadata.data = hdr;
ret = rxgk_verify_mic_skb(gk->krb5, gk->rx_Kc, &metadata,
skb, &offset, &len, &ac);
ret = rxgk_verify_mic(gk->krb5, gk->rx_Kc, &metadata, &p, &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;
call->rx_dec_offset = p - data;
call->rx_dec_len = len;
}
put_gk:
@ -522,49 +526,53 @@ static int rxgk_verify_packet_encrypted(struct rxrpc_call *call,
struct sk_buff *skb)
{
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
struct rxgk_header hdr;
unsigned int offset = sp->offset, len = sp->len;
struct rxgk_header *hdr;
unsigned int offset = 0, len = call->rx_dec_len;
void *data = call->rx_dec_buffer, *p = data;
int ret;
u32 ac = 0;
_enter("");
ret = rxgk_decrypt_skb(gk->krb5, gk->rx_enc, skb, &offset, &len, &ac);
if (crypto_krb5_check_data_len(gk->krb5, KRB5_ENCRYPT_MODE,
len, sizeof(*hdr)) < 0) {
ret = rxrpc_abort_eproto(call, skb, RXGK_PACKETSHORT,
rxgk_abort_2_short_header);
goto error;
}
ret = rxgk_decrypt(gk->krb5, gk->rx_enc, &p, &len, &ac);
if (ret < 0) {
if (ret != -ENOMEM)
rxrpc_abort_eproto(call, skb, ac, rxgk_abort_2_decrypt_eproto);
goto error;
}
offset = p - data;
if (len < sizeof(hdr)) {
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);
hdr = data + offset;
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) {
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);
call->rx_dec_offset = offset;
call->rx_dec_len = ntohl(hdr->data_len);
ret = 0;
error:
rxgk_put(gk);
@ -1076,11 +1084,12 @@ static int rxgk_sendmsg_respond_to_challenge(struct sk_buff *challenge,
* 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)
static int rxgk_verify_authenticator(struct rxrpc_connection *conn,
const struct krb5_enctype *krb5,
struct sk_buff *skb,
void *auth, unsigned int auth_len)
{
__be32 *p = auth, *end = auth + auth_len;
u32 app_len, call_count, level, epoch, cid, i;
_enter("");
@ -1143,37 +1152,6 @@ static int rxgk_do_verify_authenticator(struct rxrpc_connection *conn,
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 / sizeof(*p));
error:
kfree(auth);
return ret;
}
/*
* Verify a response.
*
@ -1184,49 +1162,45 @@ static int rxgk_verify_authenticator(struct rxrpc_connection *conn,
* };
*/
static int rxgk_verify_response(struct rxrpc_connection *conn,
struct sk_buff *skb)
struct sk_buff *skb,
void *buffer, unsigned int len)
{
const struct krb5_enctype *krb5;
struct rxrpc_key_token *token;
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
struct rxgk_response rhdr;
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;
unsigned int resp_token_len, auth_len;
void *resp_token, *auth;
__be32 xauth_len;
int ret, ec;
_enter("{%d}", conn->debug_id);
/* Parse the RXGK_Response object */
if (sizeof(rhdr) + sizeof(__be32) > len)
if (len < sizeof(*rhdr) + sizeof(__be32))
goto short_packet;
rhdr = buffer;
buffer += sizeof(*rhdr);
len -= sizeof(*rhdr);
resp_token = buffer;
resp_token_len = ntohl(rhdr->token_len);
if (resp_token_len > len ||
xdr_round_up(resp_token_len) + 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);
trace_rxrpc_rx_response(conn, sp->hdr.serial, 0, sp->hdr.cksum, resp_token_len);
token_offset = offset;
token_len = ntohl(rhdr.token_len);
if (token_len > len ||
xdr_round_up(token_len) + sizeof(__be32) > len)
goto short_packet;
buffer += xdr_round_up(resp_token_len);
len -= xdr_round_up(resp_token_len);
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);
xauth_len = *(__be32 *)buffer;
buffer += sizeof(xauth_len);
len -= sizeof(xauth_len);
auth_offset = offset;
auth = buffer;
auth_len = ntohl(xauth_len);
if (auth_len > len)
goto short_packet;
@ -1241,7 +1215,7 @@ static int rxgk_verify_response(struct rxrpc_connection *conn,
* 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);
ret = rxgk_extract_token(conn, skb, resp_token, resp_token_len, &key);
if (ret < 0)
goto out;
@ -1255,7 +1229,7 @@ static int rxgk_verify_response(struct rxrpc_connection *conn,
*/
token = key->payload.data[0];
conn->security_level = token->rxgk->level;
conn->rxgk.start_time = __be64_to_cpu(rhdr.start_time);
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)) {
@ -1265,18 +1239,18 @@ static int rxgk_verify_response(struct rxrpc_connection *conn,
krb5 = gk->krb5;
trace_rxrpc_rx_response(conn, sp->hdr.serial, krb5->etype, sp->hdr.cksum, token_len);
trace_rxrpc_rx_response(conn, sp->hdr.serial, krb5->etype, sp->hdr.cksum,
resp_token_len);
/* Decrypt, parse and verify the authenticator. */
ret = rxgk_decrypt_skb(krb5, gk->resp_enc, skb,
&auth_offset, &auth_len, &ec);
ret = rxgk_decrypt(krb5, gk->resp_enc, &auth, &auth_len, &ec);
if (ret < 0) {
rxrpc_abort_conn(conn, skb, RXGK_SEALEDINCON, ret,
rxgk_abort_resp_auth_dec);
goto out_gk;
}
ret = rxgk_verify_authenticator(conn, krb5, skb, auth_offset, auth_len);
ret = rxgk_verify_authenticator(conn, krb5, skb, auth, auth_len);
if (ret < 0)
goto out_gk;

View File

@ -40,7 +40,7 @@
* };
*/
int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb,
unsigned int ticket_offset, unsigned int ticket_len,
void *buffer, unsigned int ticket_len,
struct key **_key)
{
struct rxrpc_key_token *token;
@ -49,7 +49,7 @@ int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb,
size_t pre_ticket_len, payload_len;
unsigned int klen, enctype;
void *payload, *ticket;
__be32 *t, *p, *q, tmp[2];
__be32 *t, *p, *q, *tmp;
int ret;
_enter("");
@ -59,10 +59,7 @@ int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb,
rxgk_abort_resp_short_yfs_tkt);
/* Get the session key length */
ret = skb_copy_bits(skb, ticket_offset, tmp, sizeof(tmp));
if (ret < 0)
return rxrpc_abort_conn(conn, skb, RXGK_INCONSISTENCY, -EPROTO,
rxgk_abort_resp_short_yfs_klen);
tmp = buffer;
enctype = ntohl(tmp[0]);
klen = ntohl(tmp[1]);
@ -84,12 +81,7 @@ int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb,
* it.
*/
ticket = payload + pre_ticket_len;
ret = skb_copy_bits(skb, ticket_offset, ticket, ticket_len);
if (ret < 0) {
ret = rxrpc_abort_conn(conn, skb, RXGK_INCONSISTENCY, -EPROTO,
rxgk_abort_resp_short_yfs_tkt);
goto error;
}
memcpy(ticket, buffer, ticket_len);
/* Fill out the form header. */
p = payload;
@ -131,7 +123,7 @@ int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb,
goto error;
}
/* Ticket read in with skb_copy_bits above */
/* Ticket appended above. */
q += xdr_round_up(ticket_len) / 4;
if (WARN_ON((unsigned long)q - (unsigned long)payload != payload_len)) {
ret = -EIO;
@ -182,14 +174,15 @@ int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb,
* [tools.ietf.org/html/draft-wilkinson-afs3-rxgk-afs-08 sec 6.1]
*/
int rxgk_extract_token(struct rxrpc_connection *conn, struct sk_buff *skb,
unsigned int token_offset, unsigned int token_len,
void *token, unsigned int token_len,
struct key **_key)
{
const struct krb5_enctype *krb5;
const struct krb5_buffer *server_secret;
struct crypto_aead *token_enc = NULL;
struct key *server_key;
unsigned int ticket_offset, ticket_len;
unsigned int ticket_len;
void *ticket;
u32 kvno, enctype;
int ret, ec = 0;
@ -197,24 +190,23 @@ int rxgk_extract_token(struct rxrpc_connection *conn, struct sk_buff *skb,
__be32 kvno;
__be32 enctype;
__be32 token_len;
} container;
} *container;
if (token_len < sizeof(container))
if (token_len < sizeof(*container))
goto short_packet;
/* Decode the RXGK_TokenContainer object. This tells us which server
* key we should be using. We can then fetch the key, get the secret
* and set up the crypto to extract the token.
*/
if (skb_copy_bits(skb, token_offset, &container, sizeof(container)) < 0)
goto short_packet;
container = token;
token += sizeof(*container);
kvno = ntohl(container.kvno);
enctype = ntohl(container.enctype);
ticket_len = ntohl(container.token_len);
ticket_offset = token_offset + sizeof(container);
kvno = ntohl(container->kvno);
enctype = ntohl(container->enctype);
ticket_len = ntohl(container->token_len);
if (ticket_len > xdr_round_down(token_len - sizeof(container)))
if (ticket_len > xdr_round_down(token_len - sizeof(*container)))
goto short_packet;
_debug("KVNO %u", kvno);
@ -237,8 +229,8 @@ int rxgk_extract_token(struct rxrpc_connection *conn, struct sk_buff *skb,
* gain access to K0, from which we can derive the transport key and
* thence decode the authenticator.
*/
ret = rxgk_decrypt_skb(krb5, token_enc, skb,
&ticket_offset, &ticket_len, &ec);
ticket = token;
ret = rxgk_decrypt(krb5, token_enc, &ticket, &ticket_len, &ec);
crypto_free_aead(token_enc);
token_enc = NULL;
if (ret < 0) {
@ -248,7 +240,7 @@ int rxgk_extract_token(struct rxrpc_connection *conn, struct sk_buff *skb,
return ret;
}
ret = conn->security->default_decode_ticket(conn, skb, ticket_offset,
ret = conn->security->default_decode_ticket(conn, skb, ticket,
ticket_len, _key);
if (ret < 0)
goto cant_get_token;

View File

@ -41,10 +41,10 @@ struct rxgk_context {
* rxgk_app.c
*/
int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb,
unsigned int ticket_offset, unsigned int ticket_len,
void *ticket, unsigned int ticket_len,
struct key **_key);
int rxgk_extract_token(struct rxrpc_connection *conn, struct sk_buff *skb,
unsigned int token_offset, unsigned int token_len,
void *token, unsigned int token_len,
struct key **_key);
/*
@ -62,31 +62,30 @@ int rxgk_set_up_token_cipher(const struct krb5_buffer *server_key,
gfp_t gfp);
/*
* Apply decryption and checksumming functions to part of an skbuff. The
* offset and length are updated to reflect the actual content of the encrypted
* Apply decryption and checksumming functions a flat data buffer. The data
* point and length are updated to reflect the actual content of the encrypted
* region.
*/
static inline
int rxgk_decrypt_skb(const struct krb5_enctype *krb5,
struct crypto_aead *aead,
struct sk_buff *skb,
unsigned int *_offset, unsigned int *_len,
int *_error_code)
static inline int rxgk_decrypt(const struct krb5_enctype *krb5,
struct crypto_aead *aead,
void **_data, unsigned int *_len,
int *_error_code)
{
struct scatterlist sg[16];
struct scatterlist sg[1];
size_t offset = 0, len = *_len;
int nr_sg, ret;
int ret;
sg_init_table(sg, ARRAY_SIZE(sg));
nr_sg = skb_to_sgvec(skb, sg, *_offset, len);
if (unlikely(nr_sg < 0))
return nr_sg;
sg_init_one(sg, *_data, len);
ret = crypto_krb5_decrypt(krb5, aead, sg, nr_sg,
&offset, &len);
ret = crypto_krb5_decrypt(krb5, aead, sg, 1, &offset, &len);
switch (ret) {
case 0:
*_offset += offset;
if (offset & 3) {
*_error_code = RXGK_INCONSISTENCY;
ret = -EPROTO;
break;
}
*_data += offset;
*_len = len;
break;
case -EBADMSG: /* Checksum mismatch. */
@ -106,31 +105,26 @@ int rxgk_decrypt_skb(const struct krb5_enctype *krb5,
}
/*
* Check the MIC on a region of an skbuff. The offset and length are updated
* to reflect the actual content of the secure region.
* Check the MIC on a flat buffer. The data pointer and length are updated to
* reflect the actual content of the secure region.
*/
static inline
int rxgk_verify_mic_skb(const struct krb5_enctype *krb5,
struct crypto_shash *shash,
const struct krb5_buffer *metadata,
struct sk_buff *skb,
unsigned int *_offset, unsigned int *_len,
u32 *_error_code)
int rxgk_verify_mic(const struct krb5_enctype *krb5,
struct crypto_shash *shash,
const struct krb5_buffer *metadata,
void **_data, unsigned int *_len,
u32 *_error_code)
{
struct scatterlist sg[16];
struct scatterlist sg[1];
size_t offset = 0, len = *_len;
int nr_sg, ret;
int ret;
sg_init_table(sg, ARRAY_SIZE(sg));
nr_sg = skb_to_sgvec(skb, sg, *_offset, len);
if (unlikely(nr_sg < 0))
return nr_sg;
sg_init_one(sg, *_data, len);
ret = crypto_krb5_verify_mic(krb5, shash, metadata, sg, nr_sg,
&offset, &len);
ret = crypto_krb5_verify_mic(krb5, shash, metadata, sg, 1, &offset, &len);
switch (ret) {
case 0:
*_offset += offset;
*_data += offset;
*_len = len;
break;
case -EBADMSG: /* Checksum mismatch */

View File

@ -430,27 +430,25 @@ static int rxkad_verify_packet_1(struct rxrpc_call *call, struct sk_buff *skb,
rxrpc_seq_t seq,
struct skcipher_request *req)
{
struct rxkad_level1_hdr sechdr;
struct rxkad_level1_hdr *sechdr;
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
struct rxrpc_crypt iv;
struct scatterlist sg[16];
u32 data_size, buf;
struct scatterlist sg[1];
void *data = call->rx_dec_buffer;
u32 len = sp->len, data_size, buf;
u16 check;
int ret;
_enter("");
if (sp->len < 8)
if (len < 8)
return rxrpc_abort_eproto(call, skb, RXKADSEALEDINCON,
rxkad_abort_1_short_header);
/* Decrypt the skbuff in-place. TODO: We really want to decrypt
* directly into the target buffer.
*/
sg_init_table(sg, ARRAY_SIZE(sg));
ret = skb_to_sgvec(skb, sg, sp->offset, 8);
if (unlikely(ret < 0))
return ret;
sg_init_one(sg, data, len);
/* start the decryption afresh */
memset(&iv, 0, sizeof(iv));
@ -464,13 +462,11 @@ static int rxkad_verify_packet_1(struct rxrpc_call *call, struct sk_buff *skb,
return ret;
/* Extract the decrypted packet length */
if (skb_copy_bits(skb, sp->offset, &sechdr, sizeof(sechdr)) < 0)
return rxrpc_abort_eproto(call, skb, RXKADDATALEN,
rxkad_abort_1_short_encdata);
sp->offset += sizeof(sechdr);
sp->len -= sizeof(sechdr);
sechdr = data;
call->rx_dec_offset = sizeof(*sechdr);
len -= sizeof(*sechdr);
buf = ntohl(sechdr.data_size);
buf = ntohl(sechdr->data_size);
data_size = buf & 0xffff;
check = buf >> 16;
@ -479,10 +475,10 @@ static int rxkad_verify_packet_1(struct rxrpc_call *call, struct sk_buff *skb,
if (check != 0)
return rxrpc_abort_eproto(call, skb, RXKADSEALEDINCON,
rxkad_abort_1_short_check);
if (data_size > sp->len)
if (data_size > len)
return rxrpc_abort_eproto(call, skb, RXKADDATALEN,
rxkad_abort_1_short_data);
sp->len = data_size;
call->rx_dec_len = data_size;
_leave(" = 0 [dlen=%x]", data_size);
return 0;
@ -496,43 +492,28 @@ static int rxkad_verify_packet_2(struct rxrpc_call *call, struct sk_buff *skb,
struct skcipher_request *req)
{
const struct rxrpc_key_token *token;
struct rxkad_level2_hdr sechdr;
struct rxkad_level2_hdr *sechdr;
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
struct rxrpc_crypt iv;
struct scatterlist _sg[4], *sg;
u32 data_size, buf;
struct scatterlist sg[1];
void *data = call->rx_dec_buffer;
u32 len = sp->len, data_size, buf;
u16 check;
int nsg, ret;
int ret;
_enter(",{%d}", sp->len);
_enter(",{%d}", len);
if (sp->len < 8)
if (len < 8)
return rxrpc_abort_eproto(call, skb, RXKADSEALEDINCON,
rxkad_abort_2_short_header);
/* Don't let the crypto algo see a misaligned length. */
sp->len = round_down(sp->len, 8);
len = round_down(len, 8);
/* Decrypt the skbuff in-place. TODO: We really want to decrypt
* directly into the target buffer.
/* Decrypt in place in the call's decryption buffer. TODO: We really
* want to decrypt directly into the target buffer.
*/
sg = _sg;
nsg = skb_shinfo(skb)->nr_frags + 1;
if (nsg <= 4) {
nsg = 4;
} else {
sg = kmalloc_objs(*sg, nsg, GFP_NOIO);
if (!sg)
return -ENOMEM;
}
sg_init_table(sg, nsg);
ret = skb_to_sgvec(skb, sg, sp->offset, sp->len);
if (unlikely(ret < 0)) {
if (sg != _sg)
kfree(sg);
return ret;
}
sg_init_one(sg, data, len);
/* decrypt from the session key */
token = call->conn->key->payload.data[0];
@ -540,11 +521,9 @@ static int rxkad_verify_packet_2(struct rxrpc_call *call, struct sk_buff *skb,
skcipher_request_set_sync_tfm(req, call->conn->rxkad.cipher);
skcipher_request_set_callback(req, 0, NULL, NULL);
skcipher_request_set_crypt(req, sg, sg, sp->len, iv.x);
skcipher_request_set_crypt(req, sg, sg, len, iv.x);
ret = crypto_skcipher_decrypt(req);
skcipher_request_zero(req);
if (sg != _sg)
kfree(sg);
if (ret < 0) {
if (ret == -ENOMEM)
return ret;
@ -553,13 +532,11 @@ static int rxkad_verify_packet_2(struct rxrpc_call *call, struct sk_buff *skb,
}
/* Extract the decrypted packet length */
if (skb_copy_bits(skb, sp->offset, &sechdr, sizeof(sechdr)) < 0)
return rxrpc_abort_eproto(call, skb, RXKADDATALEN,
rxkad_abort_2_short_len);
sp->offset += sizeof(sechdr);
sp->len -= sizeof(sechdr);
sechdr = data;
call->rx_dec_offset = sizeof(*sechdr);
len -= sizeof(*sechdr);
buf = ntohl(sechdr.data_size);
buf = ntohl(sechdr->data_size);
data_size = buf & 0xffff;
check = buf >> 16;
@ -569,17 +546,18 @@ static int rxkad_verify_packet_2(struct rxrpc_call *call, struct sk_buff *skb,
return rxrpc_abort_eproto(call, skb, RXKADSEALEDINCON,
rxkad_abort_2_short_check);
if (data_size > sp->len)
if (data_size > len)
return rxrpc_abort_eproto(call, skb, RXKADDATALEN,
rxkad_abort_2_short_data);
sp->len = data_size;
call->rx_dec_len = data_size;
_leave(" = 0 [dlen=%x]", data_size);
return 0;
}
/*
* Verify the security on a received packet and the subpackets therein.
* Verify the security on a received (sub)packet. If the packet needs
* modifying (e.g. decrypting), it must be copied.
*/
static int rxkad_verify_packet(struct rxrpc_call *call, struct sk_buff *skb)
{
@ -985,7 +963,6 @@ static int rxkad_decrypt_ticket(struct rxrpc_connection *conn,
*_expiry = 0;
ASSERT(server_key->payload.data[0] != NULL);
ASSERTCMP((unsigned long) ticket & 7UL, ==, 0);
memcpy(&iv, &server_key->payload.data[2], sizeof(iv));
@ -1134,14 +1111,15 @@ static int rxkad_decrypt_response(struct rxrpc_connection *conn,
* verify a response
*/
static int rxkad_verify_response(struct rxrpc_connection *conn,
struct sk_buff *skb)
struct sk_buff *skb,
void *buffer, unsigned int len)
{
struct rxkad_response *response;
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
struct rxrpc_crypt session_key;
struct key *server_key;
time64_t expiry;
void *ticket = NULL;
void *ticket;
u32 version, kvno, ticket_len, level;
__be32 csum;
int ret, i;
@ -1164,13 +1142,8 @@ static int rxkad_verify_response(struct rxrpc_connection *conn,
}
}
ret = -ENOMEM;
response = kzalloc_obj(struct rxkad_response, GFP_NOFS);
if (!response)
goto error;
if (skb_copy_bits(skb, sizeof(struct rxrpc_wire_header),
response, sizeof(*response)) < 0) {
response = buffer;
if (len < sizeof(*response)) {
ret = rxrpc_abort_conn(conn, skb, RXKADPACKETSHORT, -EPROTO,
rxkad_abort_resp_short);
goto error;
@ -1182,6 +1155,9 @@ static int rxkad_verify_response(struct rxrpc_connection *conn,
trace_rxrpc_rx_response(conn, sp->hdr.serial, version, kvno, ticket_len);
buffer += sizeof(*response);
len -= sizeof(*response);
if (version != RXKAD_VERSION) {
ret = rxrpc_abort_conn(conn, skb, RXKADINCONSISTENCY, -EPROTO,
rxkad_abort_resp_version);
@ -1201,13 +1177,8 @@ static int rxkad_verify_response(struct rxrpc_connection *conn,
}
/* extract the kerberos ticket and decrypt and decode it */
ret = -ENOMEM;
ticket = kmalloc(ticket_len, GFP_NOFS);
if (!ticket)
goto error;
if (skb_copy_bits(skb, sizeof(struct rxrpc_wire_header) + sizeof(*response),
ticket, ticket_len) < 0) {
ticket = buffer;
if (ticket_len > len) {
ret = rxrpc_abort_conn(conn, skb, RXKADPACKETSHORT, -EPROTO,
rxkad_abort_resp_short_tkt);
goto error;
@ -1287,8 +1258,6 @@ static int rxkad_verify_response(struct rxrpc_connection *conn,
ret = rxrpc_get_server_data_key(conn, &session_key, expiry, kvno);
error:
kfree(ticket);
kfree(response);
key_put(server_key);
_leave(" = %d", ret);
return ret;