Contributors: 9
Author Tokens Token Proportion Commits Commit Proportion
Chuck Lever 842 79.28% 8 29.63%
Kevin Coffman 91 8.57% 6 22.22%
Trond Myklebust 73 6.87% 4 14.81%
J. Bruce Fields 22 2.07% 2 7.41%
Ard Biesheuvel 13 1.22% 1 3.70%
Herbert Xu 11 1.04% 3 11.11%
Jeff Layton 6 0.56% 1 3.70%
Linus Torvalds (pre-git) 3 0.28% 1 3.70%
James Morris 1 0.09% 1 3.70%
Total 1062 27


/*
 *  linux/net/sunrpc/gss_krb5_crypto.c
 *
 *  Copyright (c) 2000-2008 The Regents of the University of Michigan.
 *  All rights reserved.
 *
 *  Andy Adamson   <andros@umich.edu>
 *  Bruce Fields   <bfields@umich.edu>
 */

/*
 * Copyright (C) 1998 by the FundsXpress, INC.
 *
 * All rights reserved.
 *
 * Export of this software from the United States of America may require
 * a specific license from the United States Government.  It is the
 * responsibility of any person or organization contemplating export to
 * obtain such a license before exporting.
 *
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of FundsXpress. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  FundsXpress makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include <linux/err.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/scatterlist.h>
#include <linux/highmem.h>
#include <linux/pagemap.h>
#include <linux/sunrpc/gss_krb5.h>
#include <linux/sunrpc/xdr.h>

#include "gss_krb5_internal.h"

#if IS_ENABLED(CONFIG_SUNRPC_DEBUG)
# define RPCDBG_FACILITY        RPCDBG_AUTH
#endif


/*
 * This function makes the assumption that it was ultimately called
 * from gss_wrap().
 *
 * The client auth_gss code moves any existing tail data into a
 * separate page before calling gss_wrap.
 * The server svcauth_gss code ensures that both the head and the
 * tail have slack space of RPC_MAX_AUTH_SIZE before calling gss_wrap.
 *
 * Even with that guarantee, this function may be called more than
 * once in the processing of gss_wrap().  The best we can do is
 * verify at compile-time (see GSS_KRB5_MAX_SLACK_NEEDED) that the
 * largest expected shift will fit within RPC_MAX_AUTH_SIZE.
 * At run-time we can verify that a single invocation of this
 * function doesn't attempt to use more the RPC_MAX_AUTH_SIZE.
 */

int
xdr_extend_head(struct xdr_buf *buf, unsigned int base, unsigned int shiftlen)
{
	u8 *p;

	if (shiftlen == 0)
		return 0;

	BUG_ON(shiftlen > RPC_MAX_AUTH_SIZE);

	p = buf->head[0].iov_base + base;

	memmove(p + shiftlen, p, buf->head[0].iov_len - base);

	buf->head[0].iov_len += shiftlen;
	buf->len += shiftlen;

	return 0;
}


/**
 * gss_krb5_aead_encrypt - Encrypt a wrap token using crypto/krb5
 * @kctx: Kerberos context
 * @offset: byte offset of the GSS token header in @buf
 * @buf: OUT: send buffer
 * @pages: plaintext payload pages (page cache data)
 *
 * The xdr_buf setup mirrors the original per-enctype encrypt
 * functions, but the CBC-CTS encryption and HMAC are replaced
 * by a single AEAD operation through the crypto/krb5 library.
 *
 * Return values:
 *   %GSS_S_COMPLETE: Encryption successful
 *   %GSS_S_FAILURE: Encryption failed
 */
u32
gss_krb5_aead_encrypt(struct krb5_ctx *kctx, u32 offset,
		      struct xdr_buf *buf, struct page **pages)
{
	const struct krb5_enctype *krb5 = kctx->krb5e;
	struct crypto_aead *aead = kctx->initiate ?
		kctx->initiator_enc_aead : kctx->acceptor_enc_aead;
	unsigned int conflen = krb5->conf_len;
	unsigned int cksum_len = krb5->cksum_len;
	unsigned int sec_offset, sec_len, data_len;
	struct scatterlist sg[XDR_BUF_TO_SG_NENTS];
	struct scatterlist *sg_overflow = NULL;
	ssize_t ret;
	int nsg;

	/* Insert space for the confounder */
	if (xdr_extend_head(buf, offset + GSS_KRB5_TOK_HDR_LEN, conflen))
		return GSS_S_FAILURE;

	/* Ensure a tail segment exists */
	if (buf->tail[0].iov_base == NULL) {
		buf->tail[0].iov_base = buf->head[0].iov_base
						+ buf->head[0].iov_len;
		buf->tail[0].iov_len = 0;
	}

	/* Append a copy of the plaintext GSS token header (RFC 4121 Sec 4.2.4) */
	memcpy(buf->tail[0].iov_base + buf->tail[0].iov_len,
	       buf->head[0].iov_base + offset, GSS_KRB5_TOK_HDR_LEN);
	buf->tail[0].iov_len += GSS_KRB5_TOK_HDR_LEN;
	buf->len += GSS_KRB5_TOK_HDR_LEN;

	/* Reserve space for the integrity checksum */
	buf->tail[0].iov_len += cksum_len;
	buf->len += cksum_len;

	/*
	 * The AEAD operates in-place, but on the client send path the
	 * plaintext payload lives in page cache pages that must not be
	 * modified.  Copy the payload into the scratch output pages
	 * first.  On the server send path @pages and buf->pages are
	 * the same array, and no copy is needed.
	 *
	 * Both arrays share buf->page_base, so the same index and
	 * intra-page offset address corresponding data in each.
	 */
	if (pages != buf->pages) {
		unsigned int poff = buf->page_base;
		unsigned int plen = buf->page_len;
		unsigned int i = poff >> PAGE_SHIFT;
		unsigned int off = offset_in_page(poff);

		while (plen) {
			unsigned int n = min_t(unsigned int, plen,
					       PAGE_SIZE - off);
			memcpy_page(buf->pages[i], off, pages[i], off, n);
			plen -= n;
			i++;
			off = 0;
		}
	}

	/* Build scatterlist covering the secured region */
	sec_offset = offset + GSS_KRB5_TOK_HDR_LEN;
	sec_len = buf->len - sec_offset;
	data_len = sec_len - conflen - cksum_len;

	nsg = xdr_buf_to_sg_alloc(buf, sec_offset, sec_len,
				  sg, ARRAY_SIZE(sg),
				  &sg_overflow, GFP_NOFS);
	if (nsg < 0)
		return GSS_S_FAILURE;

	ret = crypto_krb5_encrypt(krb5, aead, sg, nsg, sec_len,
				  conflen, data_len, false);
	kfree(sg_overflow);
	if (ret < 0)
		return GSS_S_FAILURE;

	return GSS_S_COMPLETE;
}

/**
 * gss_krb5_aead_decrypt - Decrypt a wrap token using crypto/krb5
 * @kctx: Kerberos context
 * @offset: byte offset of the GSS token header in @buf
 * @len: total length of the GSS token
 * @buf: ciphertext buffer, decrypted in-place
 * @headskip: OUT: confounder length, in octets
 * @tailskip: OUT: checksum length, in octets
 *
 * Return values:
 *   %GSS_S_COMPLETE: Decryption and integrity verification succeeded
 *   %GSS_S_BAD_SIG: Integrity checksum did not match
 *   %GSS_S_DEFECTIVE_TOKEN: Token is malformed or truncated
 *   %GSS_S_FAILURE: Decryption failed
 */
u32
gss_krb5_aead_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len,
		      struct xdr_buf *buf, u32 *headskip, u32 *tailskip)
{
	const struct krb5_enctype *krb5 = kctx->krb5e;
	struct crypto_aead *aead = kctx->initiate ?
		kctx->acceptor_enc_aead : kctx->initiator_enc_aead;
	unsigned int sec_offset, sec_len;
	size_t data_offset, data_len;
	struct scatterlist sg[XDR_BUF_TO_SG_NENTS];
	struct scatterlist *sg_overflow = NULL;
	int nsg, ret;

	/* Secured region starts after the GSS token header */
	sec_offset = offset + GSS_KRB5_TOK_HDR_LEN;
	if (len < sec_offset)
		return GSS_S_DEFECTIVE_TOKEN;
	sec_len = len - sec_offset;

	nsg = xdr_buf_to_sg_alloc(buf, sec_offset, sec_len,
				  sg, ARRAY_SIZE(sg),
				  &sg_overflow, GFP_NOFS);
	if (nsg < 0)
		return GSS_S_FAILURE;

	data_offset = 0;
	data_len = sec_len;
	ret = crypto_krb5_decrypt(krb5, aead, sg, nsg,
				  &data_offset, &data_len);
	kfree(sg_overflow);
	if (ret < 0)
		return gss_krb5_errno_to_status(ret);

	*headskip = data_offset;
	*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;
}