[PATCH 09/18] SUNRPC: Switch MIC token generation to crypto/krb5
From: Chuck Lever
Date: Mon Apr 27 2026 - 10:00:36 EST
From: Chuck Lever <chuck.lever@xxxxxxxxxx>
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.
Signed-off-by: Chuck Lever <chuck.lever@xxxxxxxxxx>
---
net/sunrpc/auth_gss/gss_krb5_crypto.c | 82 +++++++++++++++++++++++++++++++++
net/sunrpc/auth_gss/gss_krb5_internal.h | 6 +++
net/sunrpc/auth_gss/gss_krb5_seal.c | 45 +++++++++++++-----
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 31c2c86b873f..3a8e6710a51b 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 ce43e1be7577..83e969494b54 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 ce540df9bce4..66c179337029 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();
--
2.53.0