/* * 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" 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 ====== */ struct alg_sock { struct sock sk; struct sockaddr_alg addr; struct sock *queued; struct hash_data hash; }; static struct alg_sock *alg_sk(struct sock *sk) { return container_of(sk, struct alg_sock, sk); } static struct proto alg_proto = { /* void (*close)(struct sock *sk, */ /* long timeout); */ /* int (*connect)(struct sock *sk, */ /* struct sockaddr *uaddr, */ /* int addr_len); */ /* int (*disconnect)(struct sock *sk, int flags); */ /* struct sock * (*accept) (struct sock *sk, int flags, int *err); */ /* int (*ioctl)(struct sock *sk, int cmd, */ /* unsigned long arg); */ /* int (*init)(struct sock *sk); */ /* void (*destroy)(struct sock *sk); */ /* void (*shutdown)(struct sock *sk, int how); */ /* 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 (*sendmsg)(struct kiocb *iocb, struct sock *sk, */ /* struct msghdr *msg, size_t len); */ /* int (*recvmsg)(struct kiocb *iocb, struct sock *sk, */ /* struct msghdr *msg, */ /* size_t len, int noblock, int flags, */ /* int *addr_len); */ /* int (*sendpage)(struct sock *sk, struct page *page, */ /* int offset, size_t size, int flags); */ /* int (*bind)(struct sock *sk, */ /* struct sockaddr *uaddr, int addr_len); */ /* int (*backlog_rcv) (struct sock *sk, */ /* struct sk_buff *skb); */ /* /\* Keeping track of sk's, looking them up, and port selection methods. *\/ */ /* void (*hash)(struct sock *sk); */ /* void (*unhash)(struct sock *sk); */ /* int (*get_port)(struct sock *sk, unsigned short snum); */ /* /\* Keeping track of sockets in use *\/ */ /* #ifdef CONFIG_PROC_FS */ /* unsigned int inuse_idx; */ /* #endif */ /* /\* Memory pressure *\/ */ /* void (*enter_memory_pressure)(struct sock *sk); */ /* 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; */ /* struct request_sock_ops *rsk_prot; */ /* struct timewait_sock_ops *twsk_prot; */ /* union { */ /* struct inet_hashinfo *hashinfo; */ /* struct udp_table *udp_table; */ /* struct raw_hashinfo *raw_hash; */ /* } h; */ .owner = THIS_MODULE, .name = "ALG", /* struct list_head node; */ /* #ifdef SOCK_REFCNT_DEBUG */ /* atomic_t socks; */ /* #endif */ }; static int alg_release(struct socket *sock) { struct sock *sk; struct alg_sock *ask; sk = sock->sk; if (sk == NULL) return 0; sock->sk = NULL; // skb_queue_purge(&sk->sk_write_queue);??? ask = alg_sk(sk); if (ask->queued != NULL) { local_bh_disable(); sock_prot_inuse_add(sock_net(ask->queued), &alg_proto, -1); local_bh_enable(); sock_put(ask->queued); } if (ask->hash.init != 0) cryptodev_hash_deinit(&ask->hash); local_bh_disable(); sock_prot_inuse_add(sock_net(sk), &alg_proto, -1); local_bh_enable(); sock_put(sk); return 0; } static int alg_bind(struct socket *sock, struct sockaddr *myaddr, int sockaddr_len) { struct sockaddr_alg *addr; struct alg_sock *ask; if (myaddr->sa_family != AF_ALG || sockaddr_len < sizeof(*addr)) return -EINVAL; addr = (struct sockaddr_alg *)myaddr; // FIXME: locking // FIXME if (strncmp(addr->salg_type, "hash", sizeof(addr->salg_type)) != 0) return -EINVAL; ask = alg_sk(sock->sk); ask->addr = *addr; return 0; } static int alg_accept(struct socket *sock, struct socket *newsock, int flags) { struct alg_sock *ask; struct sock *newsk; // FIXME: locking ask = alg_sk(sock->sk); if (ask->queued == NULL) return -EINVAL; newsk = ask->queued; ask->queued = NULL; sock_graft(newsk, newsock); return 0; } static int alg_listen(struct socket *sock, int len) { struct net *net; struct sock *newsk; struct alg_sock *ask, *newask; int res; // FIXME: locking net = sock_net(sock->sk); ask = alg_sk(sock->sk); if (ask->addr.salg_type[0] == 0) return -EINVAL; // FIXME: better error code for "not bound"? // FIXME: type-specific if (len != 1) return -EINVAL; if (ask->queued != NULL) return -EINVAL; newsk = sk_alloc(net, PF_ALG, GFP_KERNEL, &alg_proto); if (newsk == NULL) return -ENOMEM; newask = alg_sk(newsk); // FIXME res = cryptodev_hash_init(&newask->hash, ask->addr.salg_tfm, NULL, 0); if (res != 0) { sock_put(newsk); return res; } sock_init_data(NULL, newsk); local_bh_disable(); sock_prot_inuse_add(net, &alg_proto, 1); local_bh_enable(); ask->queued = newsk; return 0; } static int alg_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *m, size_t total_len) { struct alg_sock *ask; char *buf; int res; // FIXME: locking ask = alg_sk(sock->sk); if (ask->hash.init == 0) return -EINVAL; // FIXME: limit size, or use socket buffer buf = kmalloc(total_len, GFP_KERNEL); if (!buf) return -ENOMEM; res = memcpy_fromiovec(buf, m->msg_iov, total_len); if (res != 0) goto err; // FIXME res = _cryptodev_hash_update(&ask->hash, buf, total_len); if (res < 0) goto err; res = total_len; err: kfree(buf); return res; } static int alg_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *m, size_t total_len, int flags) { char digest[NCR_HASH_MAX_OUTPUT_SIZE]; struct alg_sock *ask; int res; // FIXME: locking ask = alg_sk(sock->sk); if (ask->hash.init == 0) return -EINVAL; if (total_len < ask->hash.digestsize) return -EINVAL; // FIXME BUG_ON(ask->hash.digestsize > sizeof(digest)); res = cryptodev_hash_final(&ask->hash, digest); if (res < 0) return res; res = memcpy_toiovec(m->msg_iov, digest, ask->hash.digestsize); if (res != 0) return res; res = cryptodev_hash_reset(&ask->hash); if (res != 0) return res; return ask->hash.digestsize; } 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; 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); local_bh_disable(); sock_prot_inuse_add(net, &alg_proto, 1); local_bh_enable(); return 0; } static const struct net_proto_family alg_pf = { .family = PF_ALG, .create = alg_create, .owner = THIS_MODULE }; static int __init cryptodev_register(void) { int rc; ncr_limits_init(); ncr_master_key_reset(); rc = proto_register(&alg_proto, 1); if (unlikely(rc != 0)) goto err_limits; rc = sock_register(&alg_pf); if (unlikely(rc != 0)) goto err_proto; return 0; err_proto: proto_unregister(&alg_proto); err_limits: ncr_limits_deinit(); printk(KERN_ERR PFX "registration of /dev/crypto failed\n"); return rc; } static void __exit cryptodev_deregister(void) { sock_unregister(PF_ALG); proto_unregister(&alg_proto); ncr_limits_deinit(); } /* ====== Module init/exit ====== */ static int __init init_cryptodev(void) { int rc; rc = cryptodev_register(); if (unlikely(rc)) return rc; printk(KERN_INFO PFX "driver loaded.\n"); return 0; } static void __exit exit_cryptodev(void) { cryptodev_deregister(); printk(KERN_INFO PFX "driver unloaded.\n"); } module_init(init_cryptodev); module_exit(exit_cryptodev);