rxrpc: Fix RESPONSE packet verification to extract skb to a linear buffer

This improves the fix for CVE-2026-43500.

Fix the verification of RESPONSE packets to avoid the problem of
overwriting a RESPONSE packet sent via splice to a local address by
extracting the contents of the UDP packet into a kmalloc'd linear buffer
rather than decrypting the data in place in the sk_buff (which may corrupt
the original buffer).

Fixes: 24481a7f57 ("rxrpc: Fix conn-level packet handling to unshare RESPONSE packets")
Reported-by: Hyunwoo Kim <imv4bel@gmail.com>
Closes: https://lore.kernel.org/r/afKV2zGR6rrelPC7@v4bel/
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Simon Horman <horms@kernel.org>
cc: Jiayuan Chen <jiayuan.chen@linux.dev>
cc: linux-afs@lists.infradead.org
cc: stable@kernel.org
Reviewed-by: Jeffrey Altman <jaltman@auristor.com>
Tested-by: Marc Dionne <marc.dionne@auristor.com>
Link: https://patch.msgid.link/20260515230516.2718212-4-dhowells@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
David Howells 2026-05-16 00:05:15 +01:00 committed by Jakub Kicinski
parent d2bc90cf6c
commit 8bfab4b6ff
7 changed files with 82 additions and 225 deletions

View File

@ -307,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);
};

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

@ -54,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

@ -1084,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("");
@ -1151,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.
*
@ -1192,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;
@ -1249,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;
@ -1263,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)) {
@ -1273,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);
/*
@ -61,50 +61,6 @@ int rxgk_set_up_token_cipher(const struct krb5_buffer *server_key,
const struct krb5_enctype **_krb5,
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
* 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)
{
struct scatterlist sg[16];
size_t offset = 0, len = *_len;
int nr_sg, 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;
ret = crypto_krb5_decrypt(krb5, aead, sg, nr_sg,
&offset, &len);
switch (ret) {
case 0:
*_offset += offset;
*_len = len;
break;
case -EBADMSG: /* Checksum mismatch. */
case -EPROTO:
*_error_code = RXGK_SEALEDINCON;
break;
case -EMSGSIZE:
*_error_code = RXGK_PACKETSHORT;
break;
case -ENOPKG: /* Would prefer RXGK_BADETYPE, but not available for YFS. */
default:
*_error_code = RXGK_INCONSISTENCY;
break;
}
return ret;
}
/*
* Apply decryption and checksumming functions a flat data buffer. The data
* point and length are updated to reflect the actual content of the encrypted
@ -148,50 +104,6 @@ static inline int rxgk_decrypt(const struct krb5_enctype *krb5,
return ret;
}
/*
* Check the MIC on a region of an skbuff. The offset 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)
{
struct scatterlist sg[16];
size_t offset = 0, len = *_len;
int nr_sg, 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;
ret = crypto_krb5_verify_mic(krb5, shash, metadata, sg, nr_sg,
&offset, &len);
switch (ret) {
case 0:
*_offset += offset;
*_len = len;
break;
case -EBADMSG: /* Checksum mismatch */
case -EPROTO:
*_error_code = RXGK_SEALEDINCON;
break;
case -EMSGSIZE:
*_error_code = RXGK_PACKETSHORT;
break;
case -ENOPKG: /* Would prefer RXGK_BADETYPE, but not available for YFS. */
default:
*_error_code = RXGK_INCONSISTENCY;
break;
}
return ret;
}
/*
* Check the MIC on a flat buffer. The data pointer and length are updated to
* reflect the actual content of the secure region.

View File

@ -963,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));
@ -1112,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;
@ -1142,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;
@ -1160,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);
@ -1179,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;
@ -1265,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;