root/sys/opencrypto/cryptodev.c
/*      $OpenBSD: cryptodev.c,v 1.52 2002/06/19 07:22:46 deraadt Exp $  */

/*-
 * Copyright (c) 2001 Theo de Raadt
 * Copyright (c) 2002-2006 Sam Leffler, Errno Consulting
 * Copyright (c) 2014-2021 The FreeBSD Foundation
 * All rights reserved.
 *
 * Portions of this software were developed by John-Mark Gurney
 * under sponsorship of the FreeBSD Foundation and
 * Rubicon Communications, LLC (Netgate).
 *
 * Portions of this software were developed by Ararat River
 * Consulting, LLC under sponsorship of the FreeBSD Foundation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Effort sponsored in part by the Defense Advanced Research Projects
 * Agency (DARPA) and Air Force Research Laboratory, Air Force
 * Materiel Command, USAF, under agreement number F30602-01-2-0537.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/sysctl.h>
#include <sys/errno.h>
#include <sys/random.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/fcntl.h>
#include <sys/bus.h>
#include <sys/sdt.h>
#include <sys/syscallsubr.h>

#include <opencrypto/cryptodev.h>
#include <opencrypto/xform.h>

SDT_PROVIDER_DECLARE(opencrypto);

SDT_PROBE_DEFINE1(opencrypto, dev, ioctl, error, "int"/*line number*/);

#ifdef COMPAT_FREEBSD12
/*
 * Previously, most ioctls were performed against a cloned descriptor
 * of /dev/crypto obtained via CRIOGET.  Now all ioctls are performed
 * against /dev/crypto directly.
 */
#define CRIOGET         _IOWR('c', 100, uint32_t)
#endif

/* the following are done against the cloned descriptor */

#ifdef COMPAT_FREEBSD32
#include <sys/mount.h>
#include <compat/freebsd32/freebsd32.h>

struct session_op32 {
        uint32_t        cipher;
        uint32_t        mac;
        uint32_t        keylen;
        uint32_t        key;
        int             mackeylen;
        uint32_t        mackey;
        uint32_t        ses;
};

struct session2_op32 {
        uint32_t        cipher;
        uint32_t        mac;
        uint32_t        keylen;
        uint32_t        key;
        int             mackeylen;
        uint32_t        mackey;
        uint32_t        ses;
        int             crid;
        int             ivlen;
        int             maclen;
        int             pad[2];
};

struct crypt_op32 {
        uint32_t        ses;
        uint16_t        op;
        uint16_t        flags;
        u_int           len;
        uint32_t        src, dst;
        uint32_t        mac;
        uint32_t        iv;
};

struct crypt_aead32 {
        uint32_t        ses;
        uint16_t        op;
        uint16_t        flags;
        u_int           len;
        u_int           aadlen;
        u_int           ivlen;
        uint32_t        src;
        uint32_t        dst;
        uint32_t        aad;
        uint32_t        tag;
        uint32_t        iv;
};

#define CIOCGSESSION32  _IOWR('c', 101, struct session_op32)
#define CIOCCRYPT32     _IOWR('c', 103, struct crypt_op32)
#define CIOCGSESSION232 _IOWR('c', 106, struct session2_op32)
#define CIOCCRYPTAEAD32 _IOWR('c', 109, struct crypt_aead32)

static void
session_op_from_32(const struct session_op32 *from, struct session2_op *to)
{

        memset(to, 0, sizeof(*to));
        CP(*from, *to, cipher);
        CP(*from, *to, mac);
        CP(*from, *to, keylen);
        PTRIN_CP(*from, *to, key);
        CP(*from, *to, mackeylen);
        PTRIN_CP(*from, *to, mackey);
        CP(*from, *to, ses);
        to->crid = CRYPTOCAP_F_HARDWARE;
}

static void
session2_op_from_32(const struct session2_op32 *from, struct session2_op *to)
{

        session_op_from_32((const struct session_op32 *)from, to);
        CP(*from, *to, crid);
        CP(*from, *to, ivlen);
        CP(*from, *to, maclen);
}

static void
session_op_to_32(const struct session2_op *from, struct session_op32 *to)
{

        CP(*from, *to, cipher);
        CP(*from, *to, mac);
        CP(*from, *to, keylen);
        PTROUT_CP(*from, *to, key);
        CP(*from, *to, mackeylen);
        PTROUT_CP(*from, *to, mackey);
        CP(*from, *to, ses);
}

static void
session2_op_to_32(const struct session2_op *from, struct session2_op32 *to)
{

        session_op_to_32(from, (struct session_op32 *)to);
        CP(*from, *to, crid);
}

static void
crypt_op_from_32(const struct crypt_op32 *from, struct crypt_op *to)
{

        CP(*from, *to, ses);
        CP(*from, *to, op);
        CP(*from, *to, flags);
        CP(*from, *to, len);
        PTRIN_CP(*from, *to, src);
        PTRIN_CP(*from, *to, dst);
        PTRIN_CP(*from, *to, mac);
        PTRIN_CP(*from, *to, iv);
}

static void
crypt_op_to_32(const struct crypt_op *from, struct crypt_op32 *to)
{

        CP(*from, *to, ses);
        CP(*from, *to, op);
        CP(*from, *to, flags);
        CP(*from, *to, len);
        PTROUT_CP(*from, *to, src);
        PTROUT_CP(*from, *to, dst);
        PTROUT_CP(*from, *to, mac);
        PTROUT_CP(*from, *to, iv);
}

static void
crypt_aead_from_32(const struct crypt_aead32 *from, struct crypt_aead *to)
{

        CP(*from, *to, ses);
        CP(*from, *to, op);
        CP(*from, *to, flags);
        CP(*from, *to, len);
        CP(*from, *to, aadlen);
        CP(*from, *to, ivlen);
        PTRIN_CP(*from, *to, src);
        PTRIN_CP(*from, *to, dst);
        PTRIN_CP(*from, *to, aad);
        PTRIN_CP(*from, *to, tag);
        PTRIN_CP(*from, *to, iv);
}

static void
crypt_aead_to_32(const struct crypt_aead *from, struct crypt_aead32 *to)
{

        CP(*from, *to, ses);
        CP(*from, *to, op);
        CP(*from, *to, flags);
        CP(*from, *to, len);
        CP(*from, *to, aadlen);
        CP(*from, *to, ivlen);
        PTROUT_CP(*from, *to, src);
        PTROUT_CP(*from, *to, dst);
        PTROUT_CP(*from, *to, aad);
        PTROUT_CP(*from, *to, tag);
        PTROUT_CP(*from, *to, iv);
}
#endif

static void
session2_op_from_op(const struct session_op *from, struct session2_op *to)
{

        memset(to, 0, sizeof(*to));
        memcpy(to, from, sizeof(*from));
        to->crid = CRYPTOCAP_F_HARDWARE;
}

static void
session2_op_to_op(const struct session2_op *from, struct session_op *to)
{

        memcpy(to, from, sizeof(*to));
}

struct csession {
        TAILQ_ENTRY(csession) next;
        crypto_session_t cses;
        volatile u_int  refs;
        uint32_t        ses;
        struct mtx      lock;           /* for op submission */

        u_int           blocksize;
        int             hashsize;
        int             ivsize;

        void            *key;
        void            *mackey;
};

struct cryptop_data {
        struct csession *cse;

        char            *buf;
        char            *obuf;
        char            *aad;
        bool            done;
};

struct fcrypt {
        TAILQ_HEAD(csessionlist, csession) csessions;
        int             sesn;
        struct mtx      lock;
};

static bool use_outputbuffers;
SYSCTL_BOOL(_kern_crypto, OID_AUTO, cryptodev_use_output, CTLFLAG_RW,
    &use_outputbuffers, 0,
    "Use separate output buffers for /dev/crypto requests.");

static bool use_separate_aad;
SYSCTL_BOOL(_kern_crypto, OID_AUTO, cryptodev_separate_aad, CTLFLAG_RW,
    &use_separate_aad, 0,
    "Use separate AAD buffer for /dev/crypto requests.");

static MALLOC_DEFINE(M_CRYPTODEV, "cryptodev", "/dev/crypto data buffers");

/*
 * Check a crypto identifier to see if it requested
 * a software device/driver.  This can be done either
 * by device name/class or through search constraints.
 */
static int
checkforsoftware(int *cridp)
{
        int crid;

        crid = *cridp;

        if (!crypto_devallowsoft) {
                if (crid & CRYPTOCAP_F_SOFTWARE) {
                        if (crid & CRYPTOCAP_F_HARDWARE) {
                                *cridp = CRYPTOCAP_F_HARDWARE;
                                return 0;
                        }
                        return EINVAL;
                }
                if ((crid & CRYPTOCAP_F_HARDWARE) == 0 &&
                    (crypto_getcaps(crid) & CRYPTOCAP_F_HARDWARE) == 0)
                        return EINVAL;
        }
        return 0;
}

static int
cse_create(struct fcrypt *fcr, struct session2_op *sop)
{
        struct crypto_session_params csp;
        struct csession *cse;
        const struct enc_xform *txform;
        const struct auth_hash *thash;
        void *key = NULL;
        void *mackey = NULL;
        crypto_session_t cses;
        int crid, error, mac;

        mac = sop->mac;
#ifdef COMPAT_FREEBSD12
        switch (sop->mac) {
        case CRYPTO_AES_128_NIST_GMAC:
        case CRYPTO_AES_192_NIST_GMAC:
        case CRYPTO_AES_256_NIST_GMAC:
                /* Should always be paired with GCM. */
                if (sop->cipher != CRYPTO_AES_NIST_GCM_16) {
                        CRYPTDEB("GMAC without GCM");
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        return (EINVAL);
                }
                if (sop->keylen != sop->mackeylen) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        return (EINVAL);
                }
                mac = 0;
                break;
        case CRYPTO_AES_CCM_CBC_MAC:
                /* Should always be paired with CCM. */
                if (sop->cipher != CRYPTO_AES_CCM_16) {
                        CRYPTDEB("CBC-MAC without CCM");
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        return (EINVAL);
                }
                if (sop->keylen != sop->mackeylen) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        return (EINVAL);
                }
                mac = 0;
                break;
        }
#endif

        memset(&csp, 0, sizeof(csp));
        if (use_outputbuffers)
                csp.csp_flags |= CSP_F_SEPARATE_OUTPUT;
        if (mac != 0) {
                csp.csp_auth_alg = mac;
                csp.csp_auth_klen = sop->mackeylen;
        }
        if (sop->cipher != 0) {
                csp.csp_cipher_alg = sop->cipher;
                csp.csp_cipher_klen = sop->keylen;
        }
        thash = crypto_auth_hash(&csp);
        txform = crypto_cipher(&csp);

        if (txform != NULL && txform->macsize != 0) {
                if (mac != 0) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        return (EINVAL);
                }
                csp.csp_mode = CSP_MODE_AEAD;
        } else if (txform != NULL && thash != NULL) {
                csp.csp_mode = CSP_MODE_ETA;
        } else if (txform != NULL) {
                csp.csp_mode = CSP_MODE_CIPHER;
        } else if (thash != NULL) {
                csp.csp_mode = CSP_MODE_DIGEST;
        } else {
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                return (EINVAL);
        }

        switch (csp.csp_mode) {
        case CSP_MODE_AEAD:
        case CSP_MODE_ETA:
                if (use_separate_aad)
                        csp.csp_flags |= CSP_F_SEPARATE_AAD;
                break;
        }

        if (txform != NULL) {
                if (sop->keylen > txform->maxkey ||
                    sop->keylen < txform->minkey) {
                        CRYPTDEB("invalid cipher parameters");
                        error = EINVAL;
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        goto bail;
                }

                key = malloc(csp.csp_cipher_klen, M_CRYPTODEV, M_WAITOK);
                error = copyin(sop->key, key, csp.csp_cipher_klen);
                if (error) {
                        CRYPTDEB("invalid key");
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        goto bail;
                }
                csp.csp_cipher_key = key;
                csp.csp_ivlen = txform->ivsize;
        }

        if (thash != NULL) {
                if (sop->mackeylen > thash->keysize || sop->mackeylen < 0) {
                        CRYPTDEB("invalid mac key length");
                        error = EINVAL;
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        goto bail;
                }

                if (csp.csp_auth_klen != 0) {
                        mackey = malloc(csp.csp_auth_klen, M_CRYPTODEV,
                            M_WAITOK);
                        error = copyin(sop->mackey, mackey, csp.csp_auth_klen);
                        if (error) {
                                CRYPTDEB("invalid mac key");
                                SDT_PROBE1(opencrypto, dev, ioctl, error,
                                    __LINE__);
                                goto bail;
                        }
                        csp.csp_auth_key = mackey;
                }

                if (csp.csp_auth_alg == CRYPTO_AES_NIST_GMAC)
                        csp.csp_ivlen = AES_GCM_IV_LEN;
                if (csp.csp_auth_alg == CRYPTO_AES_CCM_CBC_MAC)
                        csp.csp_ivlen = AES_CCM_IV_LEN;
        }

        if (sop->ivlen != 0) {
                if (csp.csp_ivlen == 0) {
                        CRYPTDEB("does not support an IV");
                        error = EINVAL;
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        goto bail;
                }
                csp.csp_ivlen = sop->ivlen;
        }
        if (sop->maclen != 0) {
                if (!(thash != NULL || csp.csp_mode == CSP_MODE_AEAD)) {
                        CRYPTDEB("does not support a MAC");
                        error = EINVAL;
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        goto bail;
                }
                csp.csp_auth_mlen = sop->maclen;
        }

        crid = sop->crid;
        error = checkforsoftware(&crid);
        if (error) {
                CRYPTDEB("checkforsoftware");
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                goto bail;
        }
        error = crypto_newsession(&cses, &csp, crid);
        if (error) {
                CRYPTDEB("crypto_newsession");
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                goto bail;
        }

        cse = malloc(sizeof(struct csession), M_CRYPTODEV, M_WAITOK | M_ZERO);
        mtx_init(&cse->lock, "cryptodev", "crypto session lock", MTX_DEF);
        refcount_init(&cse->refs, 1);
        cse->key = key;
        cse->mackey = mackey;
        cse->cses = cses;
        if (sop->maclen != 0)
                cse->hashsize = sop->maclen;
        else if (thash != NULL)
                cse->hashsize = thash->hashsize;
        else if (csp.csp_mode == CSP_MODE_AEAD)
                cse->hashsize = txform->macsize;
        cse->ivsize = csp.csp_ivlen;

        /*
         * NB: This isn't necessarily the block size of the underlying
         * MAC or cipher but is instead a restriction on valid input
         * sizes.
         */
        if (txform != NULL)
                cse->blocksize = txform->blocksize;
        else
                cse->blocksize = 1;

        mtx_lock(&fcr->lock);
        TAILQ_INSERT_TAIL(&fcr->csessions, cse, next);
        cse->ses = fcr->sesn++;
        mtx_unlock(&fcr->lock);

        sop->ses = cse->ses;

        /* return hardware/driver id */
        sop->crid = crypto_ses2hid(cse->cses);
bail:
        if (error) {
                free(key, M_CRYPTODEV);
                free(mackey, M_CRYPTODEV);
        }
        return (error);
}

static struct csession *
cse_find(struct fcrypt *fcr, u_int ses)
{
        struct csession *cse;

        mtx_lock(&fcr->lock);
        TAILQ_FOREACH(cse, &fcr->csessions, next) {
                if (cse->ses == ses) {
                        refcount_acquire(&cse->refs);
                        mtx_unlock(&fcr->lock);
                        return (cse);
                }
        }
        mtx_unlock(&fcr->lock);
        return (NULL);
}

static void
cse_free(struct csession *cse)
{

        if (!refcount_release(&cse->refs))
                return;
        crypto_freesession(cse->cses);
        mtx_destroy(&cse->lock);
        if (cse->key)
                free(cse->key, M_CRYPTODEV);
        if (cse->mackey)
                free(cse->mackey, M_CRYPTODEV);
        free(cse, M_CRYPTODEV);
}

static bool
cse_delete(struct fcrypt *fcr, u_int ses)
{
        struct csession *cse;

        mtx_lock(&fcr->lock);
        TAILQ_FOREACH(cse, &fcr->csessions, next) {
                if (cse->ses == ses) {
                        TAILQ_REMOVE(&fcr->csessions, cse, next);
                        mtx_unlock(&fcr->lock);
                        cse_free(cse);
                        return (true);
                }
        }
        mtx_unlock(&fcr->lock);
        return (false);
}

static struct cryptop_data *
cod_alloc(struct csession *cse, size_t aad_len, size_t len)
{
        struct cryptop_data *cod;

        cod = malloc(sizeof(struct cryptop_data), M_CRYPTODEV, M_WAITOK |
            M_ZERO);

        cod->cse = cse;
        if (crypto_get_params(cse->cses)->csp_flags & CSP_F_SEPARATE_AAD) {
                if (aad_len != 0)
                        cod->aad = malloc(aad_len, M_CRYPTODEV, M_WAITOK);
                cod->buf = malloc(len, M_CRYPTODEV, M_WAITOK);
        } else
                cod->buf = malloc(aad_len + len, M_CRYPTODEV, M_WAITOK);
        if (crypto_get_params(cse->cses)->csp_flags & CSP_F_SEPARATE_OUTPUT)
                cod->obuf = malloc(len, M_CRYPTODEV, M_WAITOK);
        return (cod);
}

static void
cod_free(struct cryptop_data *cod)
{

        free(cod->aad, M_CRYPTODEV);
        free(cod->obuf, M_CRYPTODEV);
        free(cod->buf, M_CRYPTODEV);
        free(cod, M_CRYPTODEV);
}

static int
cryptodev_cb(struct cryptop *crp)
{
        struct cryptop_data *cod = crp->crp_opaque;

        /*
         * Lock to ensure the wakeup() is not missed by the loops
         * waiting on cod->done in cryptodev_op() and
         * cryptodev_aead().
         */
        mtx_lock(&cod->cse->lock);
        cod->done = true;
        mtx_unlock(&cod->cse->lock);
        wakeup(cod);
        return (0);
}

static int
cryptodev_op(struct csession *cse, const struct crypt_op *cop)
{
        const struct crypto_session_params *csp;
        struct cryptop_data *cod = NULL;
        struct cryptop *crp = NULL;
        char *dst;
        int error;

        if (cop->len > 256*1024-4) {
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                return (E2BIG);
        }

        if ((cop->len % cse->blocksize) != 0) {
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                return (EINVAL);
        }

        if (cop->mac && cse->hashsize == 0) {
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                return (EINVAL);
        }

        /*
         * The COP_F_CIPHER_FIRST flag predates explicit session
         * modes, but the only way it was used was for EtA so allow it
         * as long as it is consistent with EtA.
         */
        if (cop->flags & COP_F_CIPHER_FIRST) {
                if (cop->op != COP_ENCRYPT) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error,  __LINE__);
                        return (EINVAL);
                }
        }

        cod = cod_alloc(cse, 0, cop->len + cse->hashsize);
        dst = cop->dst;

        crp = crypto_getreq(cse->cses, M_WAITOK);

        error = copyin(cop->src, cod->buf, cop->len);
        if (error) {
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                goto bail;
        }
        crp->crp_payload_start = 0;
        crp->crp_payload_length = cop->len;
        if (cse->hashsize)
                crp->crp_digest_start = cop->len;

        csp = crypto_get_params(cse->cses);
        switch (csp->csp_mode) {
        case CSP_MODE_COMPRESS:
                switch (cop->op) {
                case COP_ENCRYPT:
                        crp->crp_op = CRYPTO_OP_COMPRESS;
                        break;
                case COP_DECRYPT:
                        crp->crp_op = CRYPTO_OP_DECOMPRESS;
                        break;
                default:
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        error = EINVAL;
                        goto bail;
                }
                break;
        case CSP_MODE_CIPHER:
                if (cop->len == 0 ||
                    (cop->iv == NULL && cop->len == cse->ivsize)) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        error = EINVAL;
                        goto bail;
                }
                switch (cop->op) {
                case COP_ENCRYPT:
                        crp->crp_op = CRYPTO_OP_ENCRYPT;
                        break;
                case COP_DECRYPT:
                        crp->crp_op = CRYPTO_OP_DECRYPT;
                        break;
                default:
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        error = EINVAL;
                        goto bail;
                }
                break;
        case CSP_MODE_DIGEST:
                switch (cop->op) {
                case 0:
                case COP_ENCRYPT:
                case COP_DECRYPT:
                        crp->crp_op = CRYPTO_OP_COMPUTE_DIGEST;
                        if (cod->obuf != NULL)
                                crp->crp_digest_start = 0;
                        break;
                default:
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        error = EINVAL;
                        goto bail;
                }
                break;
        case CSP_MODE_AEAD:
                if (cse->ivsize != 0 && cop->iv == NULL) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        error = EINVAL;
                        goto bail;
                }
                /* FALLTHROUGH */
        case CSP_MODE_ETA:
                switch (cop->op) {
                case COP_ENCRYPT:
                        crp->crp_op = CRYPTO_OP_ENCRYPT |
                            CRYPTO_OP_COMPUTE_DIGEST;
                        break;
                case COP_DECRYPT:
                        crp->crp_op = CRYPTO_OP_DECRYPT |
                            CRYPTO_OP_VERIFY_DIGEST;
                        break;
                default:
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        error = EINVAL;
                        goto bail;
                }
                break;
        default:
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                error = EINVAL;
                goto bail;
        }

        crp->crp_flags = CRYPTO_F_CBIMM | (cop->flags & COP_F_BATCH);
        crypto_use_buf(crp, cod->buf, cop->len + cse->hashsize);
        if (cod->obuf)
                crypto_use_output_buf(crp, cod->obuf, cop->len + cse->hashsize);
        crp->crp_callback = cryptodev_cb;
        crp->crp_opaque = cod;

        if (cop->iv) {
                if (cse->ivsize == 0) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        error = EINVAL;
                        goto bail;
                }
                error = copyin(cop->iv, crp->crp_iv, cse->ivsize);
                if (error) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        goto bail;
                }
                crp->crp_flags |= CRYPTO_F_IV_SEPARATE;
        } else if (cse->ivsize != 0) {
                if (crp->crp_payload_length < cse->ivsize) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        error = EINVAL;
                        goto bail;
                }
                crp->crp_iv_start = 0;
                crp->crp_payload_length -= cse->ivsize;
                if (crp->crp_payload_length != 0)
                        crp->crp_payload_start = cse->ivsize;
                dst += cse->ivsize;
        }

        if (crp->crp_op & CRYPTO_OP_VERIFY_DIGEST) {
                error = copyin(cop->mac, cod->buf + crp->crp_digest_start,
                    cse->hashsize);
                if (error) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        goto bail;
                }
        }
again:
        /*
         * Let the dispatch run unlocked, then, interlock against the
         * callback before checking if the operation completed and going
         * to sleep.  This insures drivers don't inherit our lock which
         * results in a lock order reversal between crypto_dispatch forced
         * entry and the crypto_done callback into us.
         */
        error = crypto_dispatch(crp);
        if (error != 0) {
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                goto bail;
        }

        mtx_lock(&cse->lock);
        while (!cod->done)
                mtx_sleep(cod, &cse->lock, PWAIT, "crydev", 0);
        mtx_unlock(&cse->lock);

        if (crp->crp_etype == EAGAIN) {
                crp->crp_etype = 0;
                cod->done = false;
                goto again;
        }

        if (crp->crp_etype != 0) {
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                error = crp->crp_etype;
                goto bail;
        }

        if (cop->dst != NULL) {
                error = copyout(cod->obuf != NULL ? cod->obuf :
                    cod->buf + crp->crp_payload_start, dst,
                    crp->crp_payload_length);
                if (error) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        goto bail;
                }
        }

        if (cop->mac != NULL && (crp->crp_op & CRYPTO_OP_VERIFY_DIGEST) == 0) {
                error = copyout((cod->obuf != NULL ? cod->obuf : cod->buf) +
                    crp->crp_digest_start, cop->mac, cse->hashsize);
                if (error) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        goto bail;
                }
        }

bail:
        crypto_freereq(crp);
        cod_free(cod);

        return (error);
}

static int
cryptodev_aead(struct csession *cse, struct crypt_aead *caead)
{
        const struct crypto_session_params *csp;
        struct cryptop_data *cod = NULL;
        struct cryptop *crp = NULL;
        char *dst;
        int error;

        if (caead->len > 256*1024-4 || caead->aadlen > 256*1024-4) {
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                return (E2BIG);
        }

        if ((caead->len % cse->blocksize) != 0) {
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                return (EINVAL);
        }

        if (cse->hashsize == 0 || caead->tag == NULL) {
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                return (EINVAL);
        }

        /*
         * The COP_F_CIPHER_FIRST flag predates explicit session
         * modes, but the only way it was used was for EtA so allow it
         * as long as it is consistent with EtA.
         */
        if (caead->flags & COP_F_CIPHER_FIRST) {
                if (caead->op != COP_ENCRYPT) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error,  __LINE__);
                        return (EINVAL);
                }
        }

        cod = cod_alloc(cse, caead->aadlen, caead->len + cse->hashsize);
        dst = caead->dst;

        crp = crypto_getreq(cse->cses, M_WAITOK);

        if (cod->aad != NULL)
                error = copyin(caead->aad, cod->aad, caead->aadlen);
        else
                error = copyin(caead->aad, cod->buf, caead->aadlen);
        if (error) {
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                goto bail;
        }
        crp->crp_aad = cod->aad;
        crp->crp_aad_start = 0;
        crp->crp_aad_length = caead->aadlen;

        if (cod->aad != NULL)
                crp->crp_payload_start = 0;
        else
                crp->crp_payload_start = caead->aadlen;
        error = copyin(caead->src, cod->buf + crp->crp_payload_start,
            caead->len);
        if (error) {
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                goto bail;
        }
        crp->crp_payload_length = caead->len;
        if (caead->op == COP_ENCRYPT && cod->obuf != NULL)
                crp->crp_digest_start = crp->crp_payload_output_start +
                    caead->len;
        else
                crp->crp_digest_start = crp->crp_payload_start + caead->len;

        csp = crypto_get_params(cse->cses);
        switch (csp->csp_mode) {
        case CSP_MODE_AEAD:
        case CSP_MODE_ETA:
                switch (caead->op) {
                case COP_ENCRYPT:
                        crp->crp_op = CRYPTO_OP_ENCRYPT |
                            CRYPTO_OP_COMPUTE_DIGEST;
                        break;
                case COP_DECRYPT:
                        crp->crp_op = CRYPTO_OP_DECRYPT |
                            CRYPTO_OP_VERIFY_DIGEST;
                        break;
                default:
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        error = EINVAL;
                        goto bail;
                }
                break;
        default:
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                error = EINVAL;
                goto bail;
        }

        crp->crp_flags = CRYPTO_F_CBIMM | (caead->flags & COP_F_BATCH);
        crypto_use_buf(crp, cod->buf, crp->crp_payload_start + caead->len +
            cse->hashsize);
        if (cod->obuf != NULL)
                crypto_use_output_buf(crp, cod->obuf, caead->len +
                    cse->hashsize);
        crp->crp_callback = cryptodev_cb;
        crp->crp_opaque = cod;

        if (caead->iv) {
                /*
                 * Permit a 16-byte IV for AES-XTS, but only use the
                 * first 8 bytes as a block number.
                 */
                if (csp->csp_mode == CSP_MODE_ETA &&
                    csp->csp_cipher_alg == CRYPTO_AES_XTS &&
                    caead->ivlen == AES_BLOCK_LEN)
                        caead->ivlen = AES_XTS_IV_LEN;

                if (cse->ivsize == 0) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        error = EINVAL;
                        goto bail;
                }
                if (caead->ivlen != cse->ivsize) {
                        error = EINVAL;
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        goto bail;
                }

                error = copyin(caead->iv, crp->crp_iv, cse->ivsize);
                if (error) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        goto bail;
                }
                crp->crp_flags |= CRYPTO_F_IV_SEPARATE;
        } else {
                error = EINVAL;
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                goto bail;
        }

        if (crp->crp_op & CRYPTO_OP_VERIFY_DIGEST) {
                error = copyin(caead->tag, cod->buf + crp->crp_digest_start,
                    cse->hashsize);
                if (error) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        goto bail;
                }
        }
again:
        /*
         * Let the dispatch run unlocked, then, interlock against the
         * callback before checking if the operation completed and going
         * to sleep.  This insures drivers don't inherit our lock which
         * results in a lock order reversal between crypto_dispatch forced
         * entry and the crypto_done callback into us.
         */
        error = crypto_dispatch(crp);
        if (error != 0) {
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                goto bail;
        }

        mtx_lock(&cse->lock);
        while (!cod->done)
                mtx_sleep(cod, &cse->lock, PWAIT, "crydev", 0);
        mtx_unlock(&cse->lock);

        if (crp->crp_etype == EAGAIN) {
                crp->crp_etype = 0;
                cod->done = false;
                goto again;
        }

        if (crp->crp_etype != 0) {
                error = crp->crp_etype;
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                goto bail;
        }

        if (caead->dst != NULL) {
                error = copyout(cod->obuf != NULL ? cod->obuf :
                    cod->buf + crp->crp_payload_start, dst,
                    crp->crp_payload_length);
                if (error) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        goto bail;
                }
        }

        if ((crp->crp_op & CRYPTO_OP_VERIFY_DIGEST) == 0) {
                error = copyout((cod->obuf != NULL ? cod->obuf : cod->buf) +
                    crp->crp_digest_start, caead->tag, cse->hashsize);
                if (error) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        goto bail;
                }
        }

bail:
        crypto_freereq(crp);
        cod_free(cod);

        return (error);
}

static int
cryptodev_find(struct crypt_find_op *find)
{
        device_t dev;
        size_t fnlen = sizeof find->name;

        if (find->crid != -1) {
                dev = crypto_find_device_byhid(find->crid);
                if (dev == NULL)
                        return (ENOENT);
                strncpy(find->name, device_get_nameunit(dev), fnlen);
                find->name[fnlen - 1] = '\x0';
        } else {
                find->name[fnlen - 1] = '\x0';
                find->crid = crypto_find_driver(find->name);
                if (find->crid == -1)
                        return (ENOENT);
        }
        return (0);
}

static void
fcrypt_dtor(void *data)
{
        struct fcrypt *fcr = data;
        struct csession *cse;

        while ((cse = TAILQ_FIRST(&fcr->csessions))) {
                TAILQ_REMOVE(&fcr->csessions, cse, next);
                KASSERT(refcount_load(&cse->refs) == 1,
                    ("%s: crypto session %p with %d refs", __func__, cse,
                    refcount_load(&cse->refs)));
                cse_free(cse);
        }
        mtx_destroy(&fcr->lock);
        free(fcr, M_CRYPTODEV);
}

static int
crypto_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
{
        struct fcrypt *fcr;
        int error;

        fcr = malloc(sizeof(struct fcrypt), M_CRYPTODEV, M_WAITOK | M_ZERO);
        TAILQ_INIT(&fcr->csessions);
        mtx_init(&fcr->lock, "fcrypt", NULL, MTX_DEF);
        error = devfs_set_cdevpriv(fcr, fcrypt_dtor);
        if (error)
                fcrypt_dtor(fcr);
        return (error);
}

static int
crypto_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag,
    struct thread *td)
{
        struct fcrypt *fcr;
        struct csession *cse;
        struct session2_op *sop;
        struct crypt_op *cop;
        struct crypt_aead *caead;
        uint32_t ses;
        int error = 0;
        union {
                struct session2_op sopc;
#ifdef COMPAT_FREEBSD32
                struct crypt_op copc;
                struct crypt_aead aeadc;
#endif
        } thunk;
#ifdef COMPAT_FREEBSD32
        u_long cmd32;
        void *data32;

        cmd32 = 0;
        data32 = NULL;
        switch (cmd) {
        case CIOCGSESSION32:
                cmd32 = cmd;
                data32 = data;
                cmd = CIOCGSESSION;
                data = (void *)&thunk.sopc;
                session_op_from_32((struct session_op32 *)data32, &thunk.sopc);
                break;
        case CIOCGSESSION232:
                cmd32 = cmd;
                data32 = data;
                cmd = CIOCGSESSION2;
                data = (void *)&thunk.sopc;
                session2_op_from_32((struct session2_op32 *)data32,
                    &thunk.sopc);
                break;
        case CIOCCRYPT32:
                cmd32 = cmd;
                data32 = data;
                cmd = CIOCCRYPT;
                data = (void *)&thunk.copc;
                crypt_op_from_32((struct crypt_op32 *)data32, &thunk.copc);
                break;
        case CIOCCRYPTAEAD32:
                cmd32 = cmd;
                data32 = data;
                cmd = CIOCCRYPTAEAD;
                data = (void *)&thunk.aeadc;
                crypt_aead_from_32((struct crypt_aead32 *)data32, &thunk.aeadc);
                break;
        }
#endif

        devfs_get_cdevpriv((void **)&fcr);

        switch (cmd) {
#ifdef COMPAT_FREEBSD12
        case CRIOGET:
                /*
                 * NB: This may fail in cases that the old
                 * implementation did not if the current process has
                 * restricted filesystem access (e.g. running in a
                 * jail that does not expose /dev/crypto or in
                 * capability mode).
                 */
                error = kern_openat(td, AT_FDCWD, "/dev/crypto", UIO_SYSSPACE,
                    O_RDWR, 0);
                if (error == 0)
                        *(uint32_t *)data = td->td_retval[0];
                break;
#endif
        case CIOCGSESSION:
        case CIOCGSESSION2:
                if (cmd == CIOCGSESSION) {
                        session2_op_from_op((void *)data, &thunk.sopc);
                        sop = &thunk.sopc;
                } else
                        sop = (struct session2_op *)data;

                error = cse_create(fcr, sop);
                if (cmd == CIOCGSESSION && error == 0)
                        session2_op_to_op(sop, (void *)data);
                break;
        case CIOCFSESSION:
                ses = *(uint32_t *)data;
                if (!cse_delete(fcr, ses)) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        return (EINVAL);
                }
                break;
        case CIOCCRYPT:
                cop = (struct crypt_op *)data;
                cse = cse_find(fcr, cop->ses);
                if (cse == NULL) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        return (EINVAL);
                }
                error = cryptodev_op(cse, cop);
                cse_free(cse);
                break;
        case CIOCFINDDEV:
                error = cryptodev_find((struct crypt_find_op *)data);
                break;
        case CIOCCRYPTAEAD:
                caead = (struct crypt_aead *)data;
                cse = cse_find(fcr, caead->ses);
                if (cse == NULL) {
                        SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                        return (EINVAL);
                }
                error = cryptodev_aead(cse, caead);
                cse_free(cse);
                break;
        default:
                error = EINVAL;
                SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
                break;
        }

#ifdef COMPAT_FREEBSD32
        switch (cmd32) {
        case CIOCGSESSION32:
                if (error == 0)
                        session_op_to_32((void *)data, data32);
                break;
        case CIOCGSESSION232:
                if (error == 0)
                        session2_op_to_32((void *)data, data32);
                break;
        case CIOCCRYPT32:
                if (error == 0)
                        crypt_op_to_32((void *)data, data32);
                break;
        case CIOCCRYPTAEAD32:
                if (error == 0)
                        crypt_aead_to_32((void *)data, data32);
                break;
        }
#endif
        return (error);
}

static struct cdevsw crypto_cdevsw = {
        .d_version =    D_VERSION,
        .d_open =       crypto_open,
        .d_ioctl =      crypto_ioctl,
        .d_name =       "crypto",
};
static struct cdev *crypto_dev;

/*
 * Initialization code, both for static and dynamic loading.
 */
static int
cryptodev_modevent(module_t mod, int type, void *unused)
{
        switch (type) {
        case MOD_LOAD:
                if (bootverbose)
                        printf("crypto: <crypto device>\n");
                crypto_dev = make_dev(&crypto_cdevsw, 0, 
                                      UID_ROOT, GID_WHEEL, 0666,
                                      "crypto");
                return 0;
        case MOD_UNLOAD:
                /*XXX disallow if active sessions */
                destroy_dev(crypto_dev);
                return 0;
        }
        return EINVAL;
}

static moduledata_t cryptodev_mod = {
        "cryptodev",
        cryptodev_modevent,
        0
};
MODULE_VERSION(cryptodev, 1);
DECLARE_MODULE(cryptodev, cryptodev_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);
MODULE_DEPEND(cryptodev, crypto, 1, 1, 1);
MODULE_DEPEND(cryptodev, zlib, 1, 1, 1);