aboutsummaryrefslogtreecommitdiffstats
path: root/net
diff options
authorChuck Lever <chuck.lever@oracle.com>2026-04-27 09:50:53 -0400
committerChuck Lever <chuck.lever@oracle.com>2026-05-28 11:31:26 -0400
commitbfc12047f7f090c0cbc6a985a1f07bee5c93a9f1 (patch)
tree6f34dd7189ea2a0dcfb2e130ae099d5084c283ac /net
parentc622f2134b43ec6dcc980d4bf33b1f5b8536d3fe (diff)
downloadlinux-next-history-bfc12047f7f090c0cbc6a985a1f07bee5c93a9f1.tar.gz
SUNRPC: Switch MIC token generation to crypto/krb5
gss_krb5_get_mic_v2() currently computes the MIC checksum by driving a crypto_ahash directly, calling gss_krb5_checksum() with the message body and GSS token header. Replace this with a call to crypto_krb5_get_mic(), which performs the same keyed hash operation through the crypto/krb5 library. RFC 4121 Section 4.2.4 specifies that the checksum covers the message body followed by the token header. Because the crypto/krb5 metadata parameter is hashed before the data, the GSS header cannot be passed as metadata. Instead, the header is appended to the scatterlist after the body data, producing the correct hash input ordering without using the metadata parameter. The scatterlist layout is: [checksum_output | message_body | gss_header] The first scatterlist entry points directly into the token buffer, so the checksum is written in place. A shared helper, gss_krb5_mic_build_sg(), is introduced in gss_krb5_crypto.c to construct this scatterlist layout. The helper handles overflow allocation and scatterlist chaining for large xdr_buf page arrays. It is reused by the verify_mic counterpart in the following commit. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton <jlayton@kernel.org> Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com> Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Diffstat (limited to 'net')
-rw-r--r--net/sunrpc/auth_gss/gss_krb5_crypto.c82
-rw-r--r--net/sunrpc/auth_gss/gss_krb5_internal.h6
-rw-r--r--net/sunrpc/auth_gss/gss_krb5_seal.c45
3 files changed, 121 insertions, 12 deletions
diff --git a/net/sunrpc/auth_gss/gss_krb5_crypto.c b/net/sunrpc/auth_gss/gss_krb5_crypto.c
index 31c2c86b873fc..3a8e6710a51bc 100644
--- a/net/sunrpc/auth_gss/gss_krb5_crypto.c
+++ b/net/sunrpc/auth_gss/gss_krb5_crypto.c
@@ -1103,3 +1103,85 @@ gss_krb5_aead_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len,
*tailskip = sec_len - data_offset - data_len;
return GSS_S_COMPLETE;
}
+
+/**
+ * gss_krb5_mic_build_sg - Build scatterlist for MIC token operations
+ * @body: xdr_buf containing the message body
+ * @cksum: pointer to checksum area in the token buffer
+ * @cksum_len: length of checksum area
+ * @hdr: pointer to GSS token header
+ * @sg_head: caller-provided scatterlist array; if more than
+ * XDR_BUF_TO_SG_NENTS entries are needed, an overflow
+ * scatterlist is allocated and chained automatically
+ * @sg_overflow: OUT: overflow scatterlist, caller must kfree
+ *
+ * Per RFC 4121 Section 4.2.4, MIC token checksums cover the
+ * message body followed by the token header. The checksum
+ * output or received checksum occupies the first scatterlist
+ * entry. This layout cannot be constructed by
+ * xdr_buf_to_sg_alloc() because the checksum area and the GSS
+ * header lie outside the xdr_buf.
+ *
+ * Returns the number of scatterlist entries on success, or a
+ * negative errno on failure.
+ */
+int gss_krb5_mic_build_sg(const struct xdr_buf *body,
+ void *cksum, unsigned int cksum_len,
+ void *hdr,
+ struct scatterlist *sg_head,
+ struct scatterlist **sg_overflow)
+{
+ struct scatterlist *entry;
+ int body_max, body_nsg, nsg;
+
+ *sg_overflow = NULL;
+
+ body_max = 2;
+ if (body->page_len)
+ body_max += DIV_ROUND_UP(body->page_len +
+ offset_in_page(body->page_base),
+ PAGE_SIZE);
+ nsg = 1 + body_max + 1;
+ if (nsg <= XDR_BUF_TO_SG_NENTS) {
+ sg_init_table(sg_head, nsg);
+ } else {
+ unsigned int overflow_nents =
+ nsg - XDR_BUF_TO_SG_NENTS + 1;
+
+ *sg_overflow = kmalloc_array(overflow_nents,
+ sizeof(**sg_overflow),
+ GFP_NOFS);
+ if (!*sg_overflow)
+ return -ENOMEM;
+
+ sg_init_table(sg_head, XDR_BUF_TO_SG_NENTS);
+ sg_init_table(*sg_overflow, overflow_nents);
+ sg_chain(sg_head, XDR_BUF_TO_SG_NENTS, *sg_overflow);
+ }
+
+ sg_set_buf(&sg_head[0], cksum, cksum_len);
+ body_nsg = xdr_buf_to_sg(body, 0, body->len,
+ sg_next(&sg_head[0]), body_max);
+ if (body_nsg < 0)
+ goto out_err;
+
+ /*
+ * xdr_buf_to_sg marks the last body entry as end-of-list;
+ * clear it so the trailing header entry is reachable.
+ */
+ if (body_nsg > 0) {
+ entry = sg_last(sg_next(&sg_head[0]), body_nsg);
+ sg_unmark_end(entry);
+ entry = sg_next(entry);
+ } else {
+ entry = sg_next(&sg_head[0]);
+ }
+ sg_set_buf(entry, hdr, GSS_KRB5_TOK_HDR_LEN);
+ sg_mark_end(entry);
+ return 1 + body_nsg + 1;
+
+out_err:
+ kfree(*sg_overflow);
+ *sg_overflow = NULL;
+ return body_nsg;
+}
diff --git a/net/sunrpc/auth_gss/gss_krb5_internal.h b/net/sunrpc/auth_gss/gss_krb5_internal.h
index ce43e1be75779..83e969494b543 100644
--- a/net/sunrpc/auth_gss/gss_krb5_internal.h
+++ b/net/sunrpc/auth_gss/gss_krb5_internal.h
@@ -186,6 +186,12 @@ u32 krb5_etm_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len,
u32 gss_krb5_errno_to_status(int err);
+int gss_krb5_mic_build_sg(const struct xdr_buf *body,
+ void *cksum, unsigned int cksum_len,
+ void *hdr,
+ struct scatterlist *sg_head,
+ struct scatterlist **sg_overflow);
+
u32 gss_krb5_aead_encrypt(struct krb5_ctx *kctx, u32 offset,
struct xdr_buf *buf, struct page **pages);
u32 gss_krb5_aead_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len,
diff --git a/net/sunrpc/auth_gss/gss_krb5_seal.c b/net/sunrpc/auth_gss/gss_krb5_seal.c
index ce540df9bce46..66c1793370294 100644
--- a/net/sunrpc/auth_gss/gss_krb5_seal.c
+++ b/net/sunrpc/auth_gss/gss_krb5_seal.c
@@ -64,6 +64,8 @@
#include <linux/random.h>
#include <linux/crypto.h>
#include <linux/atomic.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
#include "gss_krb5_internal.h"
@@ -78,10 +80,10 @@ setup_token_v2(struct krb5_ctx *ctx, struct xdr_netobj *token)
void *krb5_hdr;
u8 *p, flags = 0x00;
- if ((ctx->flags & KRB5_CTX_FLAG_INITIATOR) == 0)
- flags |= 0x01;
+ if (!ctx->initiate)
+ flags |= KG2_TOKEN_FLAG_SENTBYACCEPTOR;
if (ctx->flags & KRB5_CTX_FLAG_ACCEPTOR_SUBKEY)
- flags |= 0x04;
+ flags |= KG2_TOKEN_FLAG_ACCEPTORSUBKEY;
/* Per rfc 4121, sec 4.2.6.1, there is no header,
* just start the token.
@@ -97,7 +99,7 @@ setup_token_v2(struct krb5_ctx *ctx, struct xdr_netobj *token)
*ptr++ = 0xffff;
*ptr = 0xffff;
- token->len = GSS_KRB5_TOK_HDR_LEN + ctx->gk5e->cksumlength;
+ token->len = GSS_KRB5_TOK_HDR_LEN + ctx->krb5e->cksum_len;
return krb5_hdr;
}
@@ -105,14 +107,17 @@ u32
gss_krb5_get_mic_v2(struct krb5_ctx *ctx, struct xdr_buf *text,
struct xdr_netobj *token)
{
- struct crypto_ahash *tfm = ctx->initiate ?
- ctx->initiator_sign : ctx->acceptor_sign;
- struct xdr_netobj cksumobj = {
- .len = ctx->gk5e->cksumlength,
- };
+ const struct krb5_enctype *krb5 = ctx->krb5e;
+ struct crypto_shash *shash = ctx->initiate ?
+ ctx->initiator_sign_shash : ctx->acceptor_sign_shash;
+ unsigned int cksum_len = krb5->cksum_len;
+ struct scatterlist sg_head[XDR_BUF_TO_SG_NENTS];
+ struct scatterlist *sg_overflow;
__be64 seq_send_be64;
void *krb5_hdr;
time64_t now;
+ ssize_t ret;
+ int nsg;
dprintk("RPC: %s\n", __func__);
@@ -123,9 +128,25 @@ gss_krb5_get_mic_v2(struct krb5_ctx *ctx, struct xdr_buf *text,
seq_send_be64 = cpu_to_be64(atomic64_fetch_inc(&ctx->seq_send64));
memcpy(krb5_hdr + 8, (char *) &seq_send_be64, 8);
- cksumobj.data = krb5_hdr + GSS_KRB5_TOK_HDR_LEN;
- if (gss_krb5_checksum(tfm, krb5_hdr, GSS_KRB5_TOK_HDR_LEN,
- text, 0, &cksumobj))
+ /*
+ * The checksum is written directly into the token buffer.
+ * This is safe: crypto_krb5_get_mic uses shash (software
+ * hash), so the scatterlist is never DMA-mapped.
+ */
+ nsg = gss_krb5_mic_build_sg(text,
+ krb5_hdr + GSS_KRB5_TOK_HDR_LEN,
+ cksum_len, krb5_hdr,
+ sg_head, &sg_overflow);
+ if (nsg < 0)
+ return GSS_S_FAILURE;
+
+ ret = crypto_krb5_get_mic(krb5, shash, NULL, sg_head, nsg,
+ cksum_len + text->len +
+ GSS_KRB5_TOK_HDR_LEN,
+ cksum_len,
+ text->len + GSS_KRB5_TOK_HDR_LEN);
+ kfree(sg_overflow);
+ if (ret < 0)
return GSS_S_FAILURE;
now = ktime_get_real_seconds();