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 #include #include +#include +#include #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();