| /* $OpenBSD: cryptodev.c,v 1.52 2002/06/19 07:22:46 deraadt Exp $ */ | |
| /*- | |
| * Linux port done by David McCullough <david_mccullough@mcafee.com> | |
| * Copyright (C) 2006-2010 David McCullough | |
| * Copyright (C) 2004-2005 Intel Corporation. | |
| * The license and original author are listed below. | |
| * | |
| * Copyright (c) 2001 Theo de Raadt | |
| * Copyright (c) 2002-2006 Sam Leffler, Errno Consulting | |
| * | |
| * 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. | |
| * | |
| __FBSDID("$FreeBSD: src/sys/opencrypto/cryptodev.c,v 1.34 2007/05/09 19:37:02 gnn Exp $"); | |
| */ | |
| #include <linux/version.h> | |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38) && !defined(AUTOCONF_INCLUDED) | |
| #include <linux/config.h> | |
| #endif | |
| #include <linux/types.h> | |
| #include <linux/time.h> | |
| #include <linux/delay.h> | |
| #include <linux/list.h> | |
| #include <linux/init.h> | |
| #include <linux/sched.h> | |
| #include <linux/unistd.h> | |
| #include <linux/module.h> | |
| #include <linux/wait.h> | |
| #include <linux/slab.h> | |
| #include <linux/fs.h> | |
| #include <linux/dcache.h> | |
| #include <linux/file.h> | |
| #include <linux/mount.h> | |
| #include <linux/miscdevice.h> | |
| #include <asm/uaccess.h> | |
| #include <cryptodev.h> | |
| #include <uio.h> | |
| extern asmlinkage long sys_dup(unsigned int fildes); | |
| #define debug cryptodev_debug | |
| int cryptodev_debug = 0; | |
| module_param(cryptodev_debug, int, 0644); | |
| MODULE_PARM_DESC(cryptodev_debug, "Enable cryptodev debug"); | |
| struct csession_info { | |
| u_int16_t blocksize; | |
| u_int16_t minkey, maxkey; | |
| u_int16_t keysize; | |
| /* u_int16_t hashsize; */ | |
| u_int16_t authsize; | |
| u_int16_t authkey; | |
| /* u_int16_t ctxsize; */ | |
| }; | |
| struct csession { | |
| struct list_head list; | |
| u_int64_t sid; | |
| u_int32_t ses; | |
| wait_queue_head_t waitq; | |
| u_int32_t cipher; | |
| u_int32_t mac; | |
| caddr_t key; | |
| int keylen; | |
| u_char tmp_iv[EALG_MAX_BLOCK_LEN]; | |
| caddr_t mackey; | |
| int mackeylen; | |
| struct csession_info info; | |
| struct iovec iovec; | |
| struct uio uio; | |
| int error; | |
| }; | |
| struct fcrypt { | |
| struct list_head csessions; | |
| int sesn; | |
| }; | |
| static struct csession *csefind(struct fcrypt *, u_int); | |
| static int csedelete(struct fcrypt *, struct csession *); | |
| static struct csession *cseadd(struct fcrypt *, struct csession *); | |
| static struct csession *csecreate(struct fcrypt *, u_int64_t, | |
| struct cryptoini *crie, struct cryptoini *cria, struct csession_info *); | |
| static int csefree(struct csession *); | |
| static int cryptodev_op(struct csession *, struct crypt_op *); | |
| static int cryptodev_key(struct crypt_kop *); | |
| static int cryptodev_find(struct crypt_find_op *); | |
| static int cryptodev_cb(void *); | |
| static int cryptodev_open(struct inode *inode, struct file *filp); | |
| /* | |
| * Check a crypto identifier to see if it requested | |
| * a valid crid and it's capabilities match. | |
| */ | |
| static int | |
| checkcrid(int crid) | |
| { | |
| int hid = crid & ~(CRYPTOCAP_F_SOFTWARE | CRYPTOCAP_F_HARDWARE); | |
| int typ = crid & (CRYPTOCAP_F_SOFTWARE | CRYPTOCAP_F_HARDWARE); | |
| int caps = 0; | |
| /* if the user hasn't selected a driver, then just call newsession */ | |
| if (hid == 0 && typ != 0) | |
| return 0; | |
| caps = crypto_getcaps(hid); | |
| /* didn't find anything with capabilities */ | |
| if (caps == 0) { | |
| dprintk("%s: hid=%x typ=%x not matched\n", __FUNCTION__, hid, typ); | |
| return EINVAL; | |
| } | |
| /* the user didn't specify SW or HW, so the driver is ok */ | |
| if (typ == 0) | |
| return 0; | |
| /* if the type specified didn't match */ | |
| if (typ != (caps & (CRYPTOCAP_F_SOFTWARE | CRYPTOCAP_F_HARDWARE))) { | |
| dprintk("%s: hid=%x typ=%x caps=%x not matched\n", __FUNCTION__, | |
| hid, typ, caps); | |
| return EINVAL; | |
| } | |
| return 0; | |
| } | |
| static int | |
| cryptodev_op(struct csession *cse, struct crypt_op *cop) | |
| { | |
| struct cryptop *crp = NULL; | |
| struct cryptodesc *crde = NULL, *crda = NULL; | |
| int error = 0; | |
| dprintk("%s()\n", __FUNCTION__); | |
| if (cop->len > CRYPTO_MAX_DATA_LEN) { | |
| dprintk("%s: %d > %d\n", __FUNCTION__, cop->len, CRYPTO_MAX_DATA_LEN); | |
| return (E2BIG); | |
| } | |
| if (cse->info.blocksize && (cop->len % cse->info.blocksize) != 0) { | |
| dprintk("%s: blocksize=%d len=%d\n", __FUNCTION__, cse->info.blocksize, | |
| cop->len); | |
| return (EINVAL); | |
| } | |
| cse->uio.uio_iov = &cse->iovec; | |
| cse->uio.uio_iovcnt = 1; | |
| cse->uio.uio_offset = 0; | |
| #if 0 | |
| cse->uio.uio_resid = cop->len; | |
| cse->uio.uio_segflg = UIO_SYSSPACE; | |
| cse->uio.uio_rw = UIO_WRITE; | |
| cse->uio.uio_td = td; | |
| #endif | |
| cse->uio.uio_iov[0].iov_len = cop->len; | |
| if (cse->info.authsize) | |
| cse->uio.uio_iov[0].iov_len += cse->info.authsize; | |
| cse->uio.uio_iov[0].iov_base = kmalloc(cse->uio.uio_iov[0].iov_len, | |
| GFP_KERNEL); | |
| if (cse->uio.uio_iov[0].iov_base == NULL) { | |
| dprintk("%s: iov_base kmalloc(%d) failed\n", __FUNCTION__, | |
| (int)cse->uio.uio_iov[0].iov_len); | |
| return (ENOMEM); | |
| } | |
| crp = crypto_getreq((cse->info.blocksize != 0) + (cse->info.authsize != 0)); | |
| if (crp == NULL) { | |
| dprintk("%s: ENOMEM\n", __FUNCTION__); | |
| error = ENOMEM; | |
| goto bail; | |
| } | |
| if (cse->info.authsize && cse->info.blocksize) { | |
| if (cop->op == COP_ENCRYPT) { | |
| crde = crp->crp_desc; | |
| crda = crde->crd_next; | |
| } else { | |
| crda = crp->crp_desc; | |
| crde = crda->crd_next; | |
| } | |
| } else if (cse->info.authsize) { | |
| crda = crp->crp_desc; | |
| } else if (cse->info.blocksize) { | |
| crde = crp->crp_desc; | |
| } else { | |
| dprintk("%s: bad request\n", __FUNCTION__); | |
| error = EINVAL; | |
| goto bail; | |
| } | |
| if ((error = copy_from_user(cse->uio.uio_iov[0].iov_base, cop->src, | |
| cop->len))) { | |
| dprintk("%s: bad copy\n", __FUNCTION__); | |
| goto bail; | |
| } | |
| if (crda) { | |
| crda->crd_skip = 0; | |
| crda->crd_len = cop->len; | |
| crda->crd_inject = cop->len; | |
| crda->crd_alg = cse->mac; | |
| crda->crd_key = cse->mackey; | |
| crda->crd_klen = cse->mackeylen * 8; | |
| } | |
| if (crde) { | |
| if (cop->op == COP_ENCRYPT) | |
| crde->crd_flags |= CRD_F_ENCRYPT; | |
| else | |
| crde->crd_flags &= ~CRD_F_ENCRYPT; | |
| crde->crd_len = cop->len; | |
| crde->crd_inject = 0; | |
| crde->crd_alg = cse->cipher; | |
| crde->crd_key = cse->key; | |
| crde->crd_klen = cse->keylen * 8; | |
| } | |
| crp->crp_ilen = cse->uio.uio_iov[0].iov_len; | |
| crp->crp_flags = CRYPTO_F_IOV | CRYPTO_F_CBIMM | |
| | (cop->flags & COP_F_BATCH); | |
| crp->crp_buf = (caddr_t)&cse->uio; | |
| crp->crp_callback = (int (*) (struct cryptop *)) cryptodev_cb; | |
| crp->crp_sid = cse->sid; | |
| crp->crp_opaque = (void *)cse; | |
| if (cop->iv) { | |
| if (crde == NULL) { | |
| error = EINVAL; | |
| dprintk("%s no crde\n", __FUNCTION__); | |
| goto bail; | |
| } | |
| if (cse->cipher == CRYPTO_ARC4) { /* XXX use flag? */ | |
| error = EINVAL; | |
| dprintk("%s arc4 with IV\n", __FUNCTION__); | |
| goto bail; | |
| } | |
| if ((error = copy_from_user(cse->tmp_iv, cop->iv, | |
| cse->info.blocksize))) { | |
| dprintk("%s bad iv copy\n", __FUNCTION__); | |
| goto bail; | |
| } | |
| memcpy(crde->crd_iv, cse->tmp_iv, cse->info.blocksize); | |
| crde->crd_flags |= CRD_F_IV_EXPLICIT | CRD_F_IV_PRESENT; | |
| crde->crd_skip = 0; | |
| } else if (cse->cipher == CRYPTO_ARC4) { /* XXX use flag? */ | |
| crde->crd_skip = 0; | |
| } else if (crde) { | |
| crde->crd_flags |= CRD_F_IV_PRESENT; | |
| crde->crd_skip = cse->info.blocksize; | |
| crde->crd_len -= cse->info.blocksize; | |
| } | |
| if (cop->mac && crda == NULL) { | |
| error = EINVAL; | |
| dprintk("%s no crda\n", __FUNCTION__); | |
| goto bail; | |
| } | |
| /* | |
| * 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) { | |
| dprintk("%s error in crypto_dispatch\n", __FUNCTION__); | |
| goto bail; | |
| } | |
| dprintk("%s about to WAIT\n", __FUNCTION__); | |
| /* | |
| * we really need to wait for driver to complete to maintain | |
| * state, luckily interrupts will be remembered | |
| */ | |
| do { | |
| error = wait_event_interruptible(crp->crp_waitq, | |
| ((crp->crp_flags & CRYPTO_F_DONE) != 0)); | |
| /* | |
| * we can't break out of this loop or we will leave behind | |
| * a huge mess, however, staying here means if your driver | |
| * is broken user applications can hang and not be killed. | |
| * The solution, fix your driver :-) | |
| */ | |
| if (error) { | |
| schedule(); | |
| error = 0; | |
| } | |
| } while ((crp->crp_flags & CRYPTO_F_DONE) == 0); | |
| dprintk("%s finished WAITING error=%d\n", __FUNCTION__, error); | |
| if (crp->crp_etype != 0) { | |
| error = crp->crp_etype; | |
| dprintk("%s error in crp processing\n", __FUNCTION__); | |
| goto bail; | |
| } | |
| if (cse->error) { | |
| error = cse->error; | |
| dprintk("%s error in cse processing\n", __FUNCTION__); | |
| goto bail; | |
| } | |
| if (cop->dst && (error = copy_to_user(cop->dst, | |
| cse->uio.uio_iov[0].iov_base, cop->len))) { | |
| dprintk("%s bad dst copy\n", __FUNCTION__); | |
| goto bail; | |
| } | |
| if (cop->mac && | |
| (error=copy_to_user(cop->mac, | |
| (caddr_t)cse->uio.uio_iov[0].iov_base + cop->len, | |
| cse->info.authsize))) { | |
| dprintk("%s bad mac copy\n", __FUNCTION__); | |
| goto bail; | |
| } | |
| bail: | |
| if (crp) | |
| crypto_freereq(crp); | |
| if (cse->uio.uio_iov[0].iov_base) | |
| kfree(cse->uio.uio_iov[0].iov_base); | |
| return (error); | |
| } | |
| static int | |
| cryptodev_cb(void *op) | |
| { | |
| struct cryptop *crp = (struct cryptop *) op; | |
| struct csession *cse = (struct csession *)crp->crp_opaque; | |
| int error; | |
| dprintk("%s()\n", __FUNCTION__); | |
| error = crp->crp_etype; | |
| if (error == EAGAIN) { | |
| crp->crp_flags &= ~CRYPTO_F_DONE; | |
| #ifdef NOTYET | |
| /* | |
| * DAVIDM I am fairly sure that we should turn this into a batch | |
| * request to stop bad karma/lockup, revisit | |
| */ | |
| crp->crp_flags |= CRYPTO_F_BATCH; | |
| #endif | |
| return crypto_dispatch(crp); | |
| } | |
| if (error != 0 || (crp->crp_flags & CRYPTO_F_DONE)) { | |
| cse->error = error; | |
| wake_up_interruptible(&crp->crp_waitq); | |
| } | |
| return (0); | |
| } | |
| static int | |
| cryptodevkey_cb(void *op) | |
| { | |
| struct cryptkop *krp = (struct cryptkop *) op; | |
| dprintk("%s()\n", __FUNCTION__); | |
| wake_up_interruptible(&krp->krp_waitq); | |
| return (0); | |
| } | |
| static int | |
| cryptodev_key(struct crypt_kop *kop) | |
| { | |
| struct cryptkop *krp = NULL; | |
| int error = EINVAL; | |
| int in, out, size, i; | |
| dprintk("%s()\n", __FUNCTION__); | |
| if (kop->crk_iparams + kop->crk_oparams > CRK_MAXPARAM) { | |
| dprintk("%s params too big\n", __FUNCTION__); | |
| return (EFBIG); | |
| } | |
| in = kop->crk_iparams; | |
| out = kop->crk_oparams; | |
| switch (kop->crk_op) { | |
| case CRK_MOD_EXP: | |
| if (in == 3 && out == 1) | |
| break; | |
| return (EINVAL); | |
| case CRK_MOD_EXP_CRT: | |
| if (in == 6 && out == 1) | |
| break; | |
| return (EINVAL); | |
| case CRK_DSA_SIGN: | |
| if (in == 5 && out == 2) | |
| break; | |
| return (EINVAL); | |
| case CRK_DSA_VERIFY: | |
| if (in == 7 && out == 0) | |
| break; | |
| return (EINVAL); | |
| case CRK_DH_COMPUTE_KEY: | |
| if (in == 3 && out == 1) | |
| break; | |
| return (EINVAL); | |
| default: | |
| return (EINVAL); | |
| } | |
| krp = (struct cryptkop *)kmalloc(sizeof *krp, GFP_KERNEL); | |
| if (!krp) | |
| return (ENOMEM); | |
| bzero(krp, sizeof *krp); | |
| krp->krp_op = kop->crk_op; | |
| krp->krp_status = kop->crk_status; | |
| krp->krp_iparams = kop->crk_iparams; | |
| krp->krp_oparams = kop->crk_oparams; | |
| krp->krp_crid = kop->crk_crid; | |
| krp->krp_status = 0; | |
| krp->krp_flags = CRYPTO_KF_CBIMM; | |
| krp->krp_callback = (int (*) (struct cryptkop *)) cryptodevkey_cb; | |
| init_waitqueue_head(&krp->krp_waitq); | |
| for (i = 0; i < CRK_MAXPARAM; i++) | |
| krp->krp_param[i].crp_nbits = kop->crk_param[i].crp_nbits; | |
| for (i = 0; i < krp->krp_iparams + krp->krp_oparams; i++) { | |
| size = (krp->krp_param[i].crp_nbits + 7) / 8; | |
| if (size == 0) | |
| continue; | |
| krp->krp_param[i].crp_p = (caddr_t) kmalloc(size, GFP_KERNEL); | |
| if (i >= krp->krp_iparams) | |
| continue; | |
| error = copy_from_user(krp->krp_param[i].crp_p, | |
| kop->crk_param[i].crp_p, size); | |
| if (error) | |
| goto fail; | |
| } | |
| error = crypto_kdispatch(krp); | |
| if (error) | |
| goto fail; | |
| do { | |
| error = wait_event_interruptible(krp->krp_waitq, | |
| ((krp->krp_flags & CRYPTO_KF_DONE) != 0)); | |
| /* | |
| * we can't break out of this loop or we will leave behind | |
| * a huge mess, however, staying here means if your driver | |
| * is broken user applications can hang and not be killed. | |
| * The solution, fix your driver :-) | |
| */ | |
| if (error) { | |
| schedule(); | |
| error = 0; | |
| } | |
| } while ((krp->krp_flags & CRYPTO_KF_DONE) == 0); | |
| dprintk("%s finished WAITING error=%d\n", __FUNCTION__, error); | |
| kop->crk_crid = krp->krp_crid; /* device that did the work */ | |
| if (krp->krp_status != 0) { | |
| error = krp->krp_status; | |
| goto fail; | |
| } | |
| for (i = krp->krp_iparams; i < krp->krp_iparams + krp->krp_oparams; i++) { | |
| size = (krp->krp_param[i].crp_nbits + 7) / 8; | |
| if (size == 0) | |
| continue; | |
| error = copy_to_user(kop->crk_param[i].crp_p, krp->krp_param[i].crp_p, | |
| size); | |
| if (error) | |
| goto fail; | |
| } | |
| fail: | |
| if (krp) { | |
| kop->crk_status = krp->krp_status; | |
| for (i = 0; i < CRK_MAXPARAM; i++) { | |
| if (krp->krp_param[i].crp_p) | |
| kfree(krp->krp_param[i].crp_p); | |
| } | |
| kfree(krp); | |
| } | |
| return (error); | |
| } | |
| static int | |
| cryptodev_find(struct crypt_find_op *find) | |
| { | |
| device_t dev; | |
| if (find->crid != -1) { | |
| dev = crypto_find_device_byhid(find->crid); | |
| if (dev == NULL) | |
| return (ENOENT); | |
| strlcpy(find->name, device_get_nameunit(dev), | |
| sizeof(find->name)); | |
| } else { | |
| find->crid = crypto_find_driver(find->name); | |
| if (find->crid == -1) | |
| return (ENOENT); | |
| } | |
| return (0); | |
| } | |
| static struct csession * | |
| csefind(struct fcrypt *fcr, u_int ses) | |
| { | |
| struct csession *cse; | |
| dprintk("%s()\n", __FUNCTION__); | |
| list_for_each_entry(cse, &fcr->csessions, list) | |
| if (cse->ses == ses) | |
| return (cse); | |
| return (NULL); | |
| } | |
| static int | |
| csedelete(struct fcrypt *fcr, struct csession *cse_del) | |
| { | |
| struct csession *cse; | |
| dprintk("%s()\n", __FUNCTION__); | |
| list_for_each_entry(cse, &fcr->csessions, list) { | |
| if (cse == cse_del) { | |
| list_del(&cse->list); | |
| return (1); | |
| } | |
| } | |
| return (0); | |
| } | |
| static struct csession * | |
| cseadd(struct fcrypt *fcr, struct csession *cse) | |
| { | |
| dprintk("%s()\n", __FUNCTION__); | |
| list_add_tail(&cse->list, &fcr->csessions); | |
| cse->ses = fcr->sesn++; | |
| return (cse); | |
| } | |
| static struct csession * | |
| csecreate(struct fcrypt *fcr, u_int64_t sid, struct cryptoini *crie, | |
| struct cryptoini *cria, struct csession_info *info) | |
| { | |
| struct csession *cse; | |
| dprintk("%s()\n", __FUNCTION__); | |
| cse = (struct csession *) kmalloc(sizeof(struct csession), GFP_KERNEL); | |
| if (cse == NULL) | |
| return NULL; | |
| memset(cse, 0, sizeof(struct csession)); | |
| INIT_LIST_HEAD(&cse->list); | |
| init_waitqueue_head(&cse->waitq); | |
| cse->key = crie->cri_key; | |
| cse->keylen = crie->cri_klen/8; | |
| cse->mackey = cria->cri_key; | |
| cse->mackeylen = cria->cri_klen/8; | |
| cse->sid = sid; | |
| cse->cipher = crie->cri_alg; | |
| cse->mac = cria->cri_alg; | |
| cse->info = *info; | |
| cseadd(fcr, cse); | |
| return (cse); | |
| } | |
| static int | |
| csefree(struct csession *cse) | |
| { | |
| int error; | |
| dprintk("%s()\n", __FUNCTION__); | |
| error = crypto_freesession(cse->sid); | |
| if (cse->key) | |
| kfree(cse->key); | |
| if (cse->mackey) | |
| kfree(cse->mackey); | |
| kfree(cse); | |
| return(error); | |
| } | |
| static int | |
| cryptodev_ioctl( | |
| struct inode *inode, | |
| struct file *filp, | |
| unsigned int cmd, | |
| unsigned long arg) | |
| { | |
| struct cryptoini cria, crie; | |
| struct fcrypt *fcr = filp->private_data; | |
| struct csession *cse; | |
| struct csession_info info; | |
| struct session2_op sop; | |
| struct crypt_op cop; | |
| struct crypt_kop kop; | |
| struct crypt_find_op fop; | |
| u_int64_t sid; | |
| u_int32_t ses = 0; | |
| int feat, fd, error = 0, crid; | |
| mm_segment_t fs; | |
| dprintk("%s(cmd=%x arg=%lx)\n", __FUNCTION__, cmd, arg); | |
| switch (cmd) { | |
| case CRIOGET: { | |
| dprintk("%s(CRIOGET)\n", __FUNCTION__); | |
| fs = get_fs(); | |
| set_fs(get_ds()); | |
| for (fd = 0; fd < files_fdtable(current->files)->max_fds; fd++) | |
| if (files_fdtable(current->files)->fd[fd] == filp) | |
| break; | |
| fd = sys_dup(fd); | |
| set_fs(fs); | |
| put_user(fd, (int *) arg); | |
| return IS_ERR_VALUE(fd) ? fd : 0; | |
| } | |
| #define CIOCGSESSSTR (cmd == CIOCGSESSION ? "CIOCGSESSION" : "CIOCGSESSION2") | |
| case CIOCGSESSION: | |
| case CIOCGSESSION2: | |
| dprintk("%s(%s)\n", __FUNCTION__, CIOCGSESSSTR); | |
| memset(&crie, 0, sizeof(crie)); | |
| memset(&cria, 0, sizeof(cria)); | |
| memset(&info, 0, sizeof(info)); | |
| memset(&sop, 0, sizeof(sop)); | |
| if (copy_from_user(&sop, (void*)arg, (cmd == CIOCGSESSION) ? | |
| sizeof(struct session_op) : sizeof(sop))) { | |
| dprintk("%s(%s) - bad copy\n", __FUNCTION__, CIOCGSESSSTR); | |
| error = EFAULT; | |
| goto bail; | |
| } | |
| switch (sop.cipher) { | |
| case 0: | |
| dprintk("%s(%s) - no cipher\n", __FUNCTION__, CIOCGSESSSTR); | |
| break; | |
| case CRYPTO_NULL_CBC: | |
| info.blocksize = NULL_BLOCK_LEN; | |
| info.minkey = NULL_MIN_KEY_LEN; | |
| info.maxkey = NULL_MAX_KEY_LEN; | |
| break; | |
| case CRYPTO_DES_CBC: | |
| info.blocksize = DES_BLOCK_LEN; | |
| info.minkey = DES_MIN_KEY_LEN; | |
| info.maxkey = DES_MAX_KEY_LEN; | |
| break; | |
| case CRYPTO_3DES_CBC: | |
| info.blocksize = DES3_BLOCK_LEN; | |
| info.minkey = DES3_MIN_KEY_LEN; | |
| info.maxkey = DES3_MAX_KEY_LEN; | |
| break; | |
| case CRYPTO_BLF_CBC: | |
| info.blocksize = BLOWFISH_BLOCK_LEN; | |
| info.minkey = BLOWFISH_MIN_KEY_LEN; | |
| info.maxkey = BLOWFISH_MAX_KEY_LEN; | |
| break; | |
| case CRYPTO_CAST_CBC: | |
| info.blocksize = CAST128_BLOCK_LEN; | |
| info.minkey = CAST128_MIN_KEY_LEN; | |
| info.maxkey = CAST128_MAX_KEY_LEN; | |
| break; | |
| case CRYPTO_SKIPJACK_CBC: | |
| info.blocksize = SKIPJACK_BLOCK_LEN; | |
| info.minkey = SKIPJACK_MIN_KEY_LEN; | |
| info.maxkey = SKIPJACK_MAX_KEY_LEN; | |
| break; | |
| case CRYPTO_AES_CBC: | |
| info.blocksize = AES_BLOCK_LEN; | |
| info.minkey = AES_MIN_KEY_LEN; | |
| info.maxkey = AES_MAX_KEY_LEN; | |
| break; | |
| case CRYPTO_ARC4: | |
| info.blocksize = ARC4_BLOCK_LEN; | |
| info.minkey = ARC4_MIN_KEY_LEN; | |
| info.maxkey = ARC4_MAX_KEY_LEN; | |
| break; | |
| case CRYPTO_CAMELLIA_CBC: | |
| info.blocksize = CAMELLIA_BLOCK_LEN; | |
| info.minkey = CAMELLIA_MIN_KEY_LEN; | |
| info.maxkey = CAMELLIA_MAX_KEY_LEN; | |
| break; | |
| default: | |
| dprintk("%s(%s) - bad cipher\n", __FUNCTION__, CIOCGSESSSTR); | |
| error = EINVAL; | |
| goto bail; | |
| } | |
| switch (sop.mac) { | |
| case 0: | |
| dprintk("%s(%s) - no mac\n", __FUNCTION__, CIOCGSESSSTR); | |
| break; | |
| case CRYPTO_NULL_HMAC: | |
| info.authsize = NULL_HASH_LEN; | |
| break; | |
| case CRYPTO_MD5: | |
| info.authsize = MD5_HASH_LEN; | |
| break; | |
| case CRYPTO_SHA1: | |
| info.authsize = SHA1_HASH_LEN; | |
| break; | |
| case CRYPTO_SHA2_256: | |
| info.authsize = SHA2_256_HASH_LEN; | |
| break; | |
| case CRYPTO_SHA2_384: | |
| info.authsize = SHA2_384_HASH_LEN; | |
| break; | |
| case CRYPTO_SHA2_512: | |
| info.authsize = SHA2_512_HASH_LEN; | |
| break; | |
| case CRYPTO_RIPEMD160: | |
| info.authsize = RIPEMD160_HASH_LEN; | |
| break; | |
| case CRYPTO_MD5_HMAC: | |
| info.authsize = MD5_HASH_LEN; | |
| info.authkey = 16; | |
| break; | |
| case CRYPTO_SHA1_HMAC: | |
| info.authsize = SHA1_HASH_LEN; | |
| info.authkey = 20; | |
| break; | |
| case CRYPTO_SHA2_256_HMAC: | |
| info.authsize = SHA2_256_HASH_LEN; | |
| info.authkey = 32; | |
| break; | |
| case CRYPTO_SHA2_384_HMAC: | |
| info.authsize = SHA2_384_HASH_LEN; | |
| info.authkey = 48; | |
| break; | |
| case CRYPTO_SHA2_512_HMAC: | |
| info.authsize = SHA2_512_HASH_LEN; | |
| info.authkey = 64; | |
| break; | |
| case CRYPTO_RIPEMD160_HMAC: | |
| info.authsize = RIPEMD160_HASH_LEN; | |
| info.authkey = 20; | |
| break; | |
| default: | |
| dprintk("%s(%s) - bad mac\n", __FUNCTION__, CIOCGSESSSTR); | |
| error = EINVAL; | |
| goto bail; | |
| } | |
| if (info.blocksize) { | |
| crie.cri_alg = sop.cipher; | |
| crie.cri_klen = sop.keylen * 8; | |
| if ((info.maxkey && sop.keylen > info.maxkey) || | |
| sop.keylen < info.minkey) { | |
| dprintk("%s(%s) - bad key\n", __FUNCTION__, CIOCGSESSSTR); | |
| error = EINVAL; | |
| goto bail; | |
| } | |
| crie.cri_key = (u_int8_t *) kmalloc(crie.cri_klen/8+1, GFP_KERNEL); | |
| if (copy_from_user(crie.cri_key, sop.key, | |
| crie.cri_klen/8)) { | |
| dprintk("%s(%s) - bad copy\n", __FUNCTION__, CIOCGSESSSTR); | |
| error = EFAULT; | |
| goto bail; | |
| } | |
| if (info.authsize) | |
| crie.cri_next = &cria; | |
| } | |
| if (info.authsize) { | |
| cria.cri_alg = sop.mac; | |
| cria.cri_klen = sop.mackeylen * 8; | |
| if (info.authkey && sop.mackeylen != info.authkey) { | |
| dprintk("%s(%s) - mackeylen %d != %d\n", __FUNCTION__, | |
| CIOCGSESSSTR, sop.mackeylen, info.authkey); | |
| error = EINVAL; | |
| goto bail; | |
| } | |
| if (cria.cri_klen) { | |
| cria.cri_key = (u_int8_t *) kmalloc(cria.cri_klen/8,GFP_KERNEL); | |
| if (copy_from_user(cria.cri_key, sop.mackey, | |
| cria.cri_klen / 8)) { | |
| dprintk("%s(%s) - bad copy\n", __FUNCTION__, CIOCGSESSSTR); | |
| error = EFAULT; | |
| goto bail; | |
| } | |
| } | |
| } | |
| /* NB: CIOGSESSION2 has the crid */ | |
| if (cmd == CIOCGSESSION2) { | |
| crid = sop.crid; | |
| error = checkcrid(crid); | |
| if (error) { | |
| dprintk("%s(%s) - checkcrid %x\n", __FUNCTION__, | |
| CIOCGSESSSTR, error); | |
| goto bail; | |
| } | |
| } else { | |
| /* allow either HW or SW to be used */ | |
| crid = CRYPTOCAP_F_HARDWARE | CRYPTOCAP_F_SOFTWARE; | |
| } | |
| error = crypto_newsession(&sid, (info.blocksize ? &crie : &cria), crid); | |
| if (error) { | |
| dprintk("%s(%s) - newsession %d\n",__FUNCTION__,CIOCGSESSSTR,error); | |
| goto bail; | |
| } | |
| cse = csecreate(fcr, sid, &crie, &cria, &info); | |
| if (cse == NULL) { | |
| crypto_freesession(sid); | |
| error = EINVAL; | |
| dprintk("%s(%s) - csecreate failed\n", __FUNCTION__, CIOCGSESSSTR); | |
| goto bail; | |
| } | |
| sop.ses = cse->ses; | |
| if (cmd == CIOCGSESSION2) { | |
| /* return hardware/driver id */ | |
| sop.crid = CRYPTO_SESID2HID(cse->sid); | |
| } | |
| if (copy_to_user((void*)arg, &sop, (cmd == CIOCGSESSION) ? | |
| sizeof(struct session_op) : sizeof(sop))) { | |
| dprintk("%s(%s) - bad copy\n", __FUNCTION__, CIOCGSESSSTR); | |
| error = EFAULT; | |
| } | |
| bail: | |
| if (error) { | |
| dprintk("%s(%s) - bail %d\n", __FUNCTION__, CIOCGSESSSTR, error); | |
| if (crie.cri_key) | |
| kfree(crie.cri_key); | |
| if (cria.cri_key) | |
| kfree(cria.cri_key); | |
| } | |
| break; | |
| case CIOCFSESSION: | |
| dprintk("%s(CIOCFSESSION)\n", __FUNCTION__); | |
| get_user(ses, (uint32_t*)arg); | |
| cse = csefind(fcr, ses); | |
| if (cse == NULL) { | |
| error = EINVAL; | |
| dprintk("%s(CIOCFSESSION) - Fail %d\n", __FUNCTION__, error); | |
| break; | |
| } | |
| csedelete(fcr, cse); | |
| error = csefree(cse); | |
| break; | |
| case CIOCCRYPT: | |
| dprintk("%s(CIOCCRYPT)\n", __FUNCTION__); | |
| if(copy_from_user(&cop, (void*)arg, sizeof(cop))) { | |
| dprintk("%s(CIOCCRYPT) - bad copy\n", __FUNCTION__); | |
| error = EFAULT; | |
| goto bail; | |
| } | |
| cse = csefind(fcr, cop.ses); | |
| if (cse == NULL) { | |
| error = EINVAL; | |
| dprintk("%s(CIOCCRYPT) - Fail %d\n", __FUNCTION__, error); | |
| break; | |
| } | |
| error = cryptodev_op(cse, &cop); | |
| if(copy_to_user((void*)arg, &cop, sizeof(cop))) { | |
| dprintk("%s(CIOCCRYPT) - bad return copy\n", __FUNCTION__); | |
| error = EFAULT; | |
| goto bail; | |
| } | |
| break; | |
| case CIOCKEY: | |
| case CIOCKEY2: | |
| dprintk("%s(CIOCKEY)\n", __FUNCTION__); | |
| if (!crypto_userasymcrypto) | |
| return (EPERM); /* XXX compat? */ | |
| if(copy_from_user(&kop, (void*)arg, sizeof(kop))) { | |
| dprintk("%s(CIOCKEY) - bad copy\n", __FUNCTION__); | |
| error = EFAULT; | |
| goto bail; | |
| } | |
| if (cmd == CIOCKEY) { | |
| /* NB: crypto core enforces s/w driver use */ | |
| kop.crk_crid = | |
| CRYPTOCAP_F_HARDWARE | CRYPTOCAP_F_SOFTWARE; | |
| } | |
| error = cryptodev_key(&kop); | |
| if(copy_to_user((void*)arg, &kop, sizeof(kop))) { | |
| dprintk("%s(CIOCGKEY) - bad return copy\n", __FUNCTION__); | |
| error = EFAULT; | |
| goto bail; | |
| } | |
| break; | |
| case CIOCASYMFEAT: | |
| dprintk("%s(CIOCASYMFEAT)\n", __FUNCTION__); | |
| if (!crypto_userasymcrypto) { | |
| /* | |
| * NB: if user asym crypto operations are | |
| * not permitted return "no algorithms" | |
| * so well-behaved applications will just | |
| * fallback to doing them in software. | |
| */ | |
| feat = 0; | |
| } else | |
| error = crypto_getfeat(&feat); | |
| if (!error) { | |
| error = copy_to_user((void*)arg, &feat, sizeof(feat)); | |
| } | |
| break; | |
| case CIOCFINDDEV: | |
| if (copy_from_user(&fop, (void*)arg, sizeof(fop))) { | |
| dprintk("%s(CIOCFINDDEV) - bad copy\n", __FUNCTION__); | |
| error = EFAULT; | |
| goto bail; | |
| } | |
| error = cryptodev_find(&fop); | |
| if (copy_to_user((void*)arg, &fop, sizeof(fop))) { | |
| dprintk("%s(CIOCFINDDEV) - bad return copy\n", __FUNCTION__); | |
| error = EFAULT; | |
| goto bail; | |
| } | |
| break; | |
| default: | |
| dprintk("%s(unknown ioctl 0x%x)\n", __FUNCTION__, cmd); | |
| error = EINVAL; | |
| break; | |
| } | |
| return(-error); | |
| } | |
| #ifdef HAVE_UNLOCKED_IOCTL | |
| static long | |
| cryptodev_unlocked_ioctl( | |
| struct file *filp, | |
| unsigned int cmd, | |
| unsigned long arg) | |
| { | |
| return cryptodev_ioctl(NULL, filp, cmd, arg); | |
| } | |
| #endif | |
| static int | |
| cryptodev_open(struct inode *inode, struct file *filp) | |
| { | |
| struct fcrypt *fcr; | |
| dprintk("%s()\n", __FUNCTION__); | |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) | |
| /* | |
| * on 2.6.35 private_data points to a miscdevice structure, we override | |
| * it, which is currently safe to do. | |
| */ | |
| if (filp->private_data) { | |
| printk("cryptodev: Private data already exists - %p!\n", filp->private_data); | |
| return(-ENODEV); | |
| } | |
| #endif | |
| fcr = kmalloc(sizeof(*fcr), GFP_KERNEL); | |
| if (!fcr) { | |
| dprintk("%s() - malloc failed\n", __FUNCTION__); | |
| return(-ENOMEM); | |
| } | |
| memset(fcr, 0, sizeof(*fcr)); | |
| INIT_LIST_HEAD(&fcr->csessions); | |
| filp->private_data = fcr; | |
| return(0); | |
| } | |
| static int | |
| cryptodev_release(struct inode *inode, struct file *filp) | |
| { | |
| struct fcrypt *fcr = filp->private_data; | |
| struct csession *cse, *tmp; | |
| dprintk("%s()\n", __FUNCTION__); | |
| if (!filp) { | |
| printk("cryptodev: No private data on release\n"); | |
| return(0); | |
| } | |
| list_for_each_entry_safe(cse, tmp, &fcr->csessions, list) { | |
| list_del(&cse->list); | |
| (void)csefree(cse); | |
| } | |
| filp->private_data = NULL; | |
| kfree(fcr); | |
| return(0); | |
| } | |
| static struct file_operations cryptodev_fops = { | |
| .owner = THIS_MODULE, | |
| .open = cryptodev_open, | |
| .release = cryptodev_release, | |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36) | |
| .ioctl = cryptodev_ioctl, | |
| #endif | |
| #ifdef HAVE_UNLOCKED_IOCTL | |
| .unlocked_ioctl = cryptodev_unlocked_ioctl, | |
| #endif | |
| }; | |
| static struct miscdevice cryptodev = { | |
| .minor = CRYPTODEV_MINOR, | |
| .name = "crypto", | |
| .fops = &cryptodev_fops, | |
| }; | |
| static int __init | |
| cryptodev_init(void) | |
| { | |
| int rc; | |
| dprintk("%s(%p)\n", __FUNCTION__, cryptodev_init); | |
| rc = misc_register(&cryptodev); | |
| if (rc) { | |
| printk(KERN_ERR "cryptodev: registration of /dev/crypto failed\n"); | |
| return(rc); | |
| } | |
| return(0); | |
| } | |
| static void __exit | |
| cryptodev_exit(void) | |
| { | |
| dprintk("%s()\n", __FUNCTION__); | |
| misc_deregister(&cryptodev); | |
| } | |
| module_init(cryptodev_init); | |
| module_exit(cryptodev_exit); | |
| MODULE_LICENSE("BSD"); | |
| MODULE_AUTHOR("David McCullough <david_mccullough@mcafee.com>"); | |
| MODULE_DESCRIPTION("Cryptodev (user interface to OCF)"); |