Files
ack-tegra/net/rxrpc/rxgk_common.h
T
David Howells d2bc90cf6c rxrpc: Fix DATA decrypt vs splice() by copying data to buffer in recvmsg
This improves the fix for CVE-2026-43500.

Fix the pagecache corruption from in-place decryption of a DATA packet
transmitted locally by splice() by getting rid of the packet sharing in the
I/O thread and unconditionally extracting the packet content into a bounce
buffer in which the buffer is decrypted.  recvmsg() (or the kernel
equivalent) then copies the data from the bounce buffer to the destination
buffer.  The sk_buff then remains unmodified.

This has an additional advantage in that the packet is then arranged in the
buffer with the correct alignment required for the crypto algorithms to
process directly.  The performance of the crypto does seem to be a little
faster and, surprisingly, the unencrypted performance doesn't seem to
change much - possibly due to removing complexity from the I/O thread.

Yet another advantage is that the I/O thread doesn't have to copy packets
which would slow down packet distribution, ACK generation, etc..

The buffer belongs to the call and is allocated initially at 2K,
sufficiently large to hold a whole jumbo subpacket, but the buffer will be
increased in size if needed.  However, to take this work, MSG_PEEK may
cause a later packet to be decrypted into the buffer, in which case the
earlier one will need re-decrypting for a subsequent recvmsg().

Note that rx_pkt_offset may legitimately see 0 as a valid offset now, so
switch to using USHRT_MAX to indicate an invalid offset.

Note also that I would generally prefer to replace the buffers of the
current sk_buff with a new kmalloc'd buffer of the right size, ditching the
old data and frags as this makes the handling of MSG_PEEK easier and
removes the re-decryption issue, but this looks like quite a complicated
thing to achieve.  skb_morph() looks half way to what I want, but I don't
want to have to allocate a new sk_buff.

Fixes: d0d5c0cd1e ("rxrpc: Use skb_unshare() rather than skb_cow_data()")
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
Reviewed-by: Jeffrey Altman <jaltman@auristor.com>
Tested-by: Marc Dionne <marc.dionne@auristor.com>
Link: https://patch.msgid.link/20260515230516.2718212-3-dhowells@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2026-05-20 16:36:45 -07:00

233 lines
6.0 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Common bits for GSSAPI-based RxRPC security.
*
* Copyright (C) 2025 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*/
#include <crypto/krb5.h>
#include <crypto/skcipher.h>
#include <crypto/hash.h>
/*
* Per-key number context. This is replaced when the connection is rekeyed.
*/
struct rxgk_context {
refcount_t usage;
unsigned int key_number; /* Rekeying number (goes in the rx header) */
unsigned long flags;
#define RXGK_TK_NEEDS_REKEY 0 /* Set if this needs rekeying */
unsigned long expiry; /* Expiration time of this key */
long long bytes_remaining; /* Remaining Tx lifetime of this key */
const struct krb5_enctype *krb5; /* RxGK encryption type */
const struct rxgk_key *key;
/* We need up to 7 keys derived from the transport key, but we don't
* actually need the transport key. Each key is derived by
* DK(TK,constant).
*/
struct crypto_aead *tx_enc; /* Transmission key */
struct crypto_aead *rx_enc; /* Reception key */
struct crypto_shash *tx_Kc; /* Transmission checksum key */
struct crypto_shash *rx_Kc; /* Reception checksum key */
struct crypto_aead *resp_enc; /* Response packet enc key */
};
#define xdr_round_up(x) (round_up((x), sizeof(__be32)))
#define xdr_round_down(x) (round_down((x), sizeof(__be32)))
#define xdr_object_len(x) (4 + xdr_round_up(x))
/*
* rxgk_app.c
*/
int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb,
unsigned int ticket_offset, 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,
struct key **_key);
/*
* rxgk_kdf.c
*/
void rxgk_put(struct rxgk_context *gk);
struct rxgk_context *rxgk_generate_transport_key(struct rxrpc_connection *conn,
const struct rxgk_key *key,
unsigned int key_number,
gfp_t gfp);
int rxgk_set_up_token_cipher(const struct krb5_buffer *server_key,
struct crypto_aead **token_key,
unsigned int enctype,
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
* region.
*/
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[1];
size_t offset = 0, len = *_len;
int ret;
sg_init_one(sg, *_data, len);
ret = crypto_krb5_decrypt(krb5, aead, sg, 1, &offset, &len);
switch (ret) {
case 0:
if (offset & 3) {
*_error_code = RXGK_INCONSISTENCY;
ret = -EPROTO;
break;
}
*_data += 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 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.
*/
static inline
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[1];
size_t offset = 0, len = *_len;
int ret;
sg_init_one(sg, *_data, len);
ret = crypto_krb5_verify_mic(krb5, shash, metadata, sg, 1, &offset, &len);
switch (ret) {
case 0:
*_data += 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;
}