/* * Driver for /dev/crypto device (aka CryptoDev) * * Copyright (c) 2004 Michal Ludvig , SuSE Labs * Copyright (c) 2009,2010 Nikos Mavrogiannopoulos * * This file is part of linux cryptodev. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* #include */ /* #include */ /* #include */ /* #include */ /* #include */ /* #include "cryptodev_int.h" */ /* #include */ #include #include #include #include #include #include #include #include "alg.h" #include "ncr-int.h" #undef DEBUG MODULE_AUTHOR("Nikos Mavrogiannopoulos "); MODULE_DESCRIPTION("CryptoDev driver"); MODULE_LICENSE("GPL"); /* ====== Module parameters ====== */ int cryptodev_verbosity = 0; module_param(cryptodev_verbosity, int, 0644); MODULE_PARM_DESC(cryptodev_verbosity, "0: normal, 1: verbose, 2: debug"); /* ====== CryptoAPI ====== */ void release_user_pages(struct page **pg, int pagecount) { while (pagecount--) { if (!PageReserved(pg[pagecount])) SetPageDirty(pg[pagecount]); page_cache_release(pg[pagecount]); } } /* offset of buf in it's first page */ #define PAGEOFFSET(buf) ((unsigned long)buf & ~PAGE_MASK) /* fetch the pages addr resides in into pg and initialise sg with them */ int __get_userbuf(uint8_t __user * addr, uint32_t len, int write, int pgcount, struct page **pg, struct scatterlist *sg) { int ret, pglen, i = 0; struct scatterlist *sgp; down_write(¤t->mm->mmap_sem); ret = get_user_pages(current, current->mm, (unsigned long)addr, pgcount, write, 0, pg, NULL); up_write(¤t->mm->mmap_sem); if (ret != pgcount) return -EINVAL; sg_init_table(sg, pgcount); pglen = min((ptrdiff_t) (PAGE_SIZE - PAGEOFFSET(addr)), (ptrdiff_t) len); sg_set_page(sg, pg[i++], pglen, PAGEOFFSET(addr)); len -= pglen; for (sgp = sg_next(sg); len; sgp = sg_next(sgp)) { pglen = min((uint32_t) PAGE_SIZE, len); sg_set_page(sgp, pg[i++], pglen, 0); len -= pglen; } sg_mark_end(sg_last(sg, pgcount)); return 0; } /* ====== /dev/crypto ====== */ /* A socket merely used for I/O. */ struct data_sock { struct sock sk; /* The following fields are constant between master accept() and slave close(). */ struct alg_sock *master; unsigned index; }; enum alg_op_type { OP_NONE = 0, /* bind() not performed */ OP_HASH }; /* A socket holding a crypto_tfm. */ struct alg_sock { struct sock sk; /* enum alg_op_type; Only changes once, from OP_NONE; alg_sock::mutex protects the transition. */ atomic_t op_type; struct mutex mutex; /* Protects everything below */ struct data_sock *slaves[1]; unsigned num_slaves, accept_idx; struct hash_data hash; }; /* Socket reference counting: The primary reference to alg_sock is from user-space. After listen(), data_sock instances are referenced through alg_sock::slaves. Unaccepted data_sock instancess do not not hold a reference back to alg_sock. accept() adds an alg_sock reference through data_sock::master. This creates a reference loop alg_sock<->data_sock. The loop is broken on last close() of any side of the pair, i.e. proto_ops::release, when the reference is dropped. */ static struct data_sock *data_sk(struct sock *sk) { return container_of(sk, struct data_sock, sk); } static struct alg_sock *alg_sk(struct sock *sk) { return container_of(sk, struct alg_sock, sk); } static enum alg_op_type ask_op_type(struct alg_sock *ask) { return atomic_read(&ask->op_type); } #ifdef DEBUG static void __dump_data_sock(int line, const struct data_sock *dsk) { printk(KERN_DEBUG "@%d: %p: cnt %d, master %p, index %u\n", line, dsk, atomic_read(&dsk->sk.sk_refcnt), dsk->master, dsk->index); } #define DUMP_DSK(DSK) (__dump_data_sock(__LINE__, (DSK))) static void __dump_alg_sock(int line, const struct alg_sock *ask) { static const char *const types[] = { [OP_NONE] = "OP_NONE", [OP_HASH] = "OP_HASH", }; enum alg_op_type type; const char *type_name; char type_buf[32]; type = ask_op_type(ask); if (type < ARRAY_SIZE(types) && types[type] != NULL) type_name = types[type]; else { sprintf(type_buf, "%d", (int)type); type_name = type_buf; } printk(KERN_DEBUG "@%d: %p: cnt %d, %s, slave %p: %u/%u, hash %d\n", line, ask, atomic_read(&ask->sk.sk_refcnt), type_name, ask->slaves[0], ask->accept_idx, ask->num_slaves, ask->hash.init); } #define DUMP_ASK(ASK) (__dump_alg_sock(__LINE__, (ASK))) #else /* !DEBUG */ #define DUMP_DSK(DSK) ((void)0) #define DUMP_ASK(ASK) ((void)0) #endif /* !DEBUG */ static struct proto data_proto = { /* int (*init)(struct sock *sk); */ /* void (*destroy)(struct sock *sk); */ /* int (*setsockopt)(struct sock *sk, int level, */ /* int optname, char __user *optval, */ /* unsigned int optlen); */ /* int (*getsockopt)(struct sock *sk, int level, */ /* int optname, char __user *optval, */ /* int __user *option); */ /* #ifdef CONFIG_COMPAT */ /* int (*compat_setsockopt)(struct sock *sk, */ /* int level, */ /* int optname, char __user *optval, */ /* unsigned int optlen); */ /* int (*compat_getsockopt)(struct sock *sk, */ /* int level, */ /* int optname, char __user *optval, */ /* int __user *option); */ /* #endif */ /* int (*recvmsg)(struct kiocb *iocb, struct sock *sk, */ /* struct msghdr *msg, */ /* size_t len, int noblock, int flags, */ /* int *addr_len); */ /* /\* Memory pressure *\/ */ /* atomic_t *memory_allocated; /\* Current allocated memory. *\/ */ /* struct percpu_counter *sockets_allocated; /\* Current number of sockets. *\/ */ /* /\* */ /* * Pressure flag: try to collapse. */ /* * Technical note: it is used by multiple contexts non atomically. */ /* * All the __sk_mem_schedule() is of this nature: accounting */ /* * is strict, actions are advisory and have some latency. */ /* *\/ */ /* int *memory_pressure; */ /* int *sysctl_mem; */ /* int *sysctl_wmem; */ /* int *sysctl_rmem; */ /* int max_header; */ .obj_size = sizeof(struct data_sock), /* int slab_flags; */ /* struct percpu_counter *orphan_count; */ .owner = THIS_MODULE, .name = "ALG-data", }; static struct proto alg_proto = { /* int (*init)(struct sock *sk); */ /* void (*destroy)(struct sock *sk); */ /* int (*setsockopt)(struct sock *sk, int level, */ /* int optname, char __user *optval, */ /* unsigned int optlen); */ /* int (*getsockopt)(struct sock *sk, int level, */ /* int optname, char __user *optval, */ /* int __user *option); */ /* #ifdef CONFIG_COMPAT */ /* int (*compat_setsockopt)(struct sock *sk, */ /* int level, */ /* int optname, char __user *optval, */ /* unsigned int optlen); */ /* int (*compat_getsockopt)(struct sock *sk, */ /* int level, */ /* int optname, char __user *optval, */ /* int __user *option); */ /* #endif */ /* int (*recvmsg)(struct kiocb *iocb, struct sock *sk, */ /* struct msghdr *msg, */ /* size_t len, int noblock, int flags, */ /* int *addr_len); */ /* /\* Memory pressure *\/ */ /* atomic_t *memory_allocated; /\* Current allocated memory. *\/ */ /* struct percpu_counter *sockets_allocated; /\* Current number of sockets. *\/ */ /* /\* */ /* * Pressure flag: try to collapse. */ /* * Technical note: it is used by multiple contexts non atomically. */ /* * All the __sk_mem_schedule() is of this nature: accounting */ /* * is strict, actions are advisory and have some latency. */ /* *\/ */ /* int *memory_pressure; */ /* int *sysctl_mem; */ /* int *sysctl_wmem; */ /* int *sysctl_rmem; */ /* int max_header; */ .obj_size = sizeof(struct alg_sock), /* int slab_flags; */ /* struct percpu_counter *orphan_count; */ .owner = THIS_MODULE, .name = "ALG", }; /* We only call sock_put() from task context, so this is running in task context as well. */ static void data_destruct(struct sock *sk) { DUMP_DSK(data_sk(sk)); local_bh_disable(); sock_prot_inuse_add(sock_net(sk), &data_proto, -1); local_bh_enable(); } static int data_release(struct socket *sock) { struct sock *sk; struct data_sock *dsk; sk = sock->sk; if (unlikely(sk == NULL)) return 0; dsk = data_sk(sk); DUMP_DSK(dsk); BUG_ON(dsk->master == NULL); sock_put(&dsk->master->sk); dsk->master = NULL; DUMP_DSK(dsk); sock_put(sk); DUMP_DSK(dsk); return 0; } static int do_data_sendmsg(struct kiocb *iocb, struct alg_sock *ask, unsigned index, struct msghdr *m, size_t total_len) { char *buf; int res; DUMP_ASK(ask); switch (ask_op_type(ask)) { case OP_HASH: BUG_ON(index != 0); BUG_ON(ask->hash.init == 0); // FIXME: limit size, or use socket buffer, or refer to // userspace pages directly buf = kmalloc(total_len, GFP_KERNEL); if (!buf) return -ENOMEM; res = memcpy_fromiovec(buf, m->msg_iov, total_len); if (res != 0) goto err; mutex_lock(&ask->mutex); res = _cryptodev_hash_update(&ask->hash, buf, total_len); mutex_unlock(&ask->mutex); if (res < 0) goto err; res = total_len; break; default: BUG(); } err: kfree(buf); return res; } static int data_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *m, size_t total_len) { struct data_sock *dsk; dsk = data_sk(sock->sk); DUMP_DSK(dsk); BUG_ON(dsk->master == NULL); return do_data_sendmsg(iocb, dsk->master, dsk->index, m, total_len); } static int do_data_recvmsg(struct kiocb *iocb, struct alg_sock *ask, unsigned index, struct msghdr *m, size_t total_len, int flags) { DUMP_ASK(ask); switch (ask_op_type(ask)) { case OP_HASH: { char digest[NCR_HASH_MAX_OUTPUT_SIZE]; int res; BUG_ON(index != 0); BUG_ON(ask->hash.init == 0); if (total_len < ask->hash.digestsize) return -EINVAL; BUG_ON(ask->hash.digestsize > sizeof(digest)); mutex_lock(&ask->mutex); res = cryptodev_hash_final(&ask->hash, digest); if (res >= 0) res = cryptodev_hash_reset(&ask->hash); mutex_unlock(&ask->mutex); if (res < 0) return res; res = memcpy_toiovec(m->msg_iov, digest, ask->hash.digestsize); if (res != 0) return res; return ask->hash.digestsize; } default: BUG(); } } static int data_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *m, size_t total_len, int flags) { struct data_sock *dsk; dsk = data_sk(sock->sk); DUMP_DSK(dsk); BUG_ON(dsk->master == NULL); return do_data_recvmsg(iocb, dsk->master, dsk->index, m, total_len, flags); } static const struct proto_ops data_proto_ops = { .family = PF_ALG, .owner = THIS_MODULE, .release = data_release, .bind = sock_no_bind, .connect = sock_no_connect, .socketpair = sock_no_socketpair, .accept = sock_no_accept, .getname = sock_no_getname, .poll = sock_no_poll, .ioctl = sock_no_ioctl, .compat_ioctl = NULL, .listen = sock_no_listen, .shutdown = sock_no_shutdown, .setsockopt = sock_no_setsockopt, .getsockopt = sock_no_getsockopt, .compat_setsockopt = NULL, .compat_getsockopt = NULL, .sendmsg = data_sendmsg, .recvmsg = data_recvmsg, .mmap = sock_no_mmap, .sendpage = NULL, .splice_read = NULL, }; /* We only call sock_put() from task context, so this is running in task context as well. */ static void alg_destruct(struct sock *sk) { struct alg_sock *ask; ask = alg_sk(sk); DUMP_ASK(ask); switch (ask_op_type(ask)) { case OP_NONE: break; case OP_HASH: /* Locking is not necessary, this is the only reference. */ cryptodev_hash_deinit(&ask->hash); break; default: BUG(); } local_bh_disable(); sock_prot_inuse_add(sock_net(sk), &alg_proto, -1); local_bh_enable(); mutex_destroy(&ask->mutex); DUMP_ASK(ask); } static int alg_release(struct socket *sock) { struct sock *sk; struct alg_sock *ask; size_t i; sk = sock->sk; if (unlikely(sk == NULL)) return 0; sock->sk = NULL; ask = alg_sk(sk); DUMP_ASK(ask); // skb_queue_purge(&sk->sk_write_queue);??? /* Locking ask->mutex is not necessary here, ask->{slaves,num_slaves, accept_idx} can only be manipulated through sock. */ for (i = 0; i < ask->num_slaves; i++) { struct data_sock *dsk; dsk = ask->slaves[i]; ask->slaves[i] = NULL; BUG_ON(dsk == NULL); DUMP_DSK(dsk); sock_put(&dsk->sk); DUMP_DSK(dsk); } DUMP_ASK(ask); sock_put(sk); DUMP_ASK(ask); return 0; } static int alg_bind(struct socket *sock, struct sockaddr *myaddr, int sockaddr_len) { struct sockaddr_alg *addr; struct alg_sock *ask; int res; if (myaddr->sa_family != AF_ALG) return -EAFNOSUPPORT; if (sockaddr_len < sizeof(*addr)) return -EINVAL; addr = (struct sockaddr_alg *)myaddr; if (memchr(addr->salg_type, '\0', sizeof(addr->salg_type)) == NULL) return -EINVAL; ask = alg_sk(sock->sk); DUMP_ASK(ask); mutex_lock(&ask->mutex); if (ask_op_type(ask) != OP_NONE) { res = -EINVAL; goto out_unlock; } if (strncmp(addr->salg_type, "hash", sizeof(addr->salg_type)) == 0) { BUG_ON(ask->hash.init != 0); res = cryptodev_hash_init(&ask->hash, addr->salg_tfm, NULL, 0); if (res != 0) goto out_unlock; atomic_set(&ask->op_type, OP_HASH); } else { res = -EINVAL; goto out_unlock; } res = 0; out_unlock: mutex_unlock(&ask->mutex); DUMP_ASK(ask); return res; } static int alg_accept(struct socket *sock, struct socket *newsock, int flags) { struct alg_sock *ask; struct data_sock *dsk; ask = alg_sk(sock->sk); DUMP_ASK(ask); mutex_lock(&ask->mutex); if (ask->accept_idx >= ask->num_slaves) { mutex_unlock(&ask->mutex); return -EINVAL; } dsk = ask->slaves[ask->accept_idx]; ask->accept_idx++; mutex_unlock(&ask->mutex); DUMP_DSK(dsk); DUMP_ASK(ask); sock_hold(&ask->sk); DUMP_DSK(dsk); dsk->master = ask; DUMP_ASK(ask); sock_hold(&dsk->sk); DUMP_DSK(dsk); sock_graft(&dsk->sk, newsock); DUMP_DSK(dsk); newsock->ops = &data_proto_ops; return 0; } static int alg_listen(struct socket *sock, int len) { struct net *net; struct alg_sock *ask; struct data_sock *dsk; size_t i; int res; ask = alg_sk(sock->sk); DUMP_ASK(ask); switch (ask_op_type(ask)) { case OP_NONE: return -EDESTADDRREQ; case OP_HASH: if (len != 1) return -EINVAL; break; default: BUG(); } BUG_ON(len > ARRAY_SIZE(ask->slaves)); mutex_lock(&ask->mutex); if (ask->num_slaves != 0) { res = -EINVAL; goto out_unlock; } net = sock_net(sock->sk); for (i = 0; i < len; i++) { struct sock *newsk; newsk = sk_alloc(net, PF_ALG, GFP_KERNEL, &data_proto); if (newsk == NULL) goto err_partial; sock_init_data(NULL, newsk); local_bh_disable(); sock_prot_inuse_add(net, &data_proto, 1); local_bh_enable(); newsk->sk_destruct = data_destruct; dsk = data_sk(newsk); dsk->index = i; DUMP_DSK(dsk); ask->slaves[i] = dsk; } ask->num_slaves = len; res = 0; out_unlock: mutex_unlock(&ask->mutex); DUMP_ASK(ask); return res; err_partial: while (i != 0) { i--; dsk = ask->slaves[i]; ask->slaves[i] = NULL; BUG_ON(dsk == NULL); DUMP_DSK(dsk); sock_put(&dsk->sk); DUMP_DSK(dsk); } res = -ENOMEM; goto out_unlock; } static int alg_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *m, size_t total_len) { struct alg_sock *ask; ask = alg_sk(sock->sk); DUMP_ASK(ask); if (ask_op_type(ask) == OP_NONE) return -ENOTCONN; return do_data_sendmsg(iocb, ask, 0, m, total_len); } static int alg_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *m, size_t total_len, int flags) { struct alg_sock *ask; ask = alg_sk(sock->sk); DUMP_ASK(ask); if (ask_op_type(ask) == OP_NONE) return -ENOTCONN; return do_data_recvmsg(iocb, ask, 0, m, total_len, flags); } static const struct proto_ops alg_proto_ops = { .family = PF_ALG, .owner = THIS_MODULE, .release = alg_release, .bind = alg_bind, .connect = sock_no_connect, .socketpair = sock_no_socketpair, .accept = alg_accept, .getname = sock_no_getname, .poll = sock_no_poll, .ioctl = sock_no_ioctl, .compat_ioctl = NULL, .listen = alg_listen, .shutdown = sock_no_shutdown, .setsockopt = sock_no_setsockopt, .getsockopt = sock_no_getsockopt, .compat_setsockopt = NULL, .compat_getsockopt = NULL, .sendmsg = alg_sendmsg, .recvmsg = alg_recvmsg, .mmap = sock_no_mmap, .sendpage = NULL, .splice_read = NULL, }; static int alg_create(struct net *net, struct socket *sock, int protocol, int kern) { struct sock *sk; struct alg_sock *ask; if (sock->type != SOCK_STREAM) return -ESOCKTNOSUPPORT; if (protocol != 0) return -EPROTONOSUPPORT; sock->ops = &alg_proto_ops; sk = sk_alloc(net, PF_ALG, GFP_KERNEL, &alg_proto); if (sk == NULL) return -ENOMEM; sock_init_data(sock, sk); ask = alg_sk(sk); mutex_init(&ask->mutex); local_bh_disable(); sock_prot_inuse_add(net, &alg_proto, 1); local_bh_enable(); sk->sk_destruct = alg_destruct; return 0; } static const struct net_proto_family alg_pf = { .family = PF_ALG, .create = alg_create, .owner = THIS_MODULE }; static int __init init_cryptodev(void) { int rc; ncr_master_key_reset(); rc = proto_register(&alg_proto, 1); if (unlikely(rc != 0)) goto err; rc = proto_register(&data_proto, 1); if (unlikely(rc != 0)) goto err_alg_proto; rc = sock_register(&alg_pf); if (unlikely(rc != 0)) goto err_data_proto; printk(KERN_INFO PFX "driver loaded.\n"); return 0; err_data_proto: proto_unregister(&data_proto); err_alg_proto: proto_unregister(&alg_proto); err: printk(KERN_ERR PFX "driver registration failed\n"); return rc; } static void __exit exit_cryptodev(void) { sock_unregister(PF_ALG); proto_unregister(&data_proto); proto_unregister(&alg_proto); printk(KERN_INFO PFX "driver unloaded.\n"); } module_init(init_cryptodev); module_exit(exit_cryptodev);