 0686952458
			
		
	
	
	0686952458
	
	
	
		
			
			Change data type to fix warning: crypto/af_alg.c:35: warning: initialization from incompatible pointer type Signed-off-by: Randy Dunlap <randy.dunlap@oracle.com> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
		
			
				
	
	
		
			483 lines
		
	
	
	
		
			9.3 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			483 lines
		
	
	
	
		
			9.3 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * af_alg: User-space algorithm interface
 | |
|  *
 | |
|  * This file provides the user-space API for algorithms.
 | |
|  *
 | |
|  * Copyright (c) 2010 Herbert Xu <herbert@gondor.apana.org.au>
 | |
|  *
 | |
|  * 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.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #include <asm/atomic.h>
 | |
| #include <crypto/if_alg.h>
 | |
| #include <linux/crypto.h>
 | |
| #include <linux/init.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/list.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/net.h>
 | |
| #include <linux/rwsem.h>
 | |
| 
 | |
| struct alg_type_list {
 | |
| 	const struct af_alg_type *type;
 | |
| 	struct list_head list;
 | |
| };
 | |
| 
 | |
| static atomic_long_t alg_memory_allocated;
 | |
| 
 | |
| static struct proto alg_proto = {
 | |
| 	.name			= "ALG",
 | |
| 	.owner			= THIS_MODULE,
 | |
| 	.memory_allocated	= &alg_memory_allocated,
 | |
| 	.obj_size		= sizeof(struct alg_sock),
 | |
| };
 | |
| 
 | |
| static LIST_HEAD(alg_types);
 | |
| static DECLARE_RWSEM(alg_types_sem);
 | |
| 
 | |
| static const struct af_alg_type *alg_get_type(const char *name)
 | |
| {
 | |
| 	const struct af_alg_type *type = ERR_PTR(-ENOENT);
 | |
| 	struct alg_type_list *node;
 | |
| 
 | |
| 	down_read(&alg_types_sem);
 | |
| 	list_for_each_entry(node, &alg_types, list) {
 | |
| 		if (strcmp(node->type->name, name))
 | |
| 			continue;
 | |
| 
 | |
| 		if (try_module_get(node->type->owner))
 | |
| 			type = node->type;
 | |
| 		break;
 | |
| 	}
 | |
| 	up_read(&alg_types_sem);
 | |
| 
 | |
| 	return type;
 | |
| }
 | |
| 
 | |
| int af_alg_register_type(const struct af_alg_type *type)
 | |
| {
 | |
| 	struct alg_type_list *node;
 | |
| 	int err = -EEXIST;
 | |
| 
 | |
| 	down_write(&alg_types_sem);
 | |
| 	list_for_each_entry(node, &alg_types, list) {
 | |
| 		if (!strcmp(node->type->name, type->name))
 | |
| 			goto unlock;
 | |
| 	}
 | |
| 
 | |
| 	node = kmalloc(sizeof(*node), GFP_KERNEL);
 | |
| 	err = -ENOMEM;
 | |
| 	if (!node)
 | |
| 		goto unlock;
 | |
| 
 | |
| 	type->ops->owner = THIS_MODULE;
 | |
| 	node->type = type;
 | |
| 	list_add(&node->list, &alg_types);
 | |
| 	err = 0;
 | |
| 
 | |
| unlock:
 | |
| 	up_write(&alg_types_sem);
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(af_alg_register_type);
 | |
| 
 | |
| int af_alg_unregister_type(const struct af_alg_type *type)
 | |
| {
 | |
| 	struct alg_type_list *node;
 | |
| 	int err = -ENOENT;
 | |
| 
 | |
| 	down_write(&alg_types_sem);
 | |
| 	list_for_each_entry(node, &alg_types, list) {
 | |
| 		if (strcmp(node->type->name, type->name))
 | |
| 			continue;
 | |
| 
 | |
| 		list_del(&node->list);
 | |
| 		kfree(node);
 | |
| 		err = 0;
 | |
| 		break;
 | |
| 	}
 | |
| 	up_write(&alg_types_sem);
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(af_alg_unregister_type);
 | |
| 
 | |
| static void alg_do_release(const struct af_alg_type *type, void *private)
 | |
| {
 | |
| 	if (!type)
 | |
| 		return;
 | |
| 
 | |
| 	type->release(private);
 | |
| 	module_put(type->owner);
 | |
| }
 | |
| 
 | |
| int af_alg_release(struct socket *sock)
 | |
| {
 | |
| 	if (sock->sk)
 | |
| 		sock_put(sock->sk);
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(af_alg_release);
 | |
| 
 | |
| static int alg_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
 | |
| {
 | |
| 	struct sock *sk = sock->sk;
 | |
| 	struct alg_sock *ask = alg_sk(sk);
 | |
| 	struct sockaddr_alg *sa = (void *)uaddr;
 | |
| 	const struct af_alg_type *type;
 | |
| 	void *private;
 | |
| 
 | |
| 	if (sock->state == SS_CONNECTED)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (addr_len != sizeof(*sa))
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	sa->salg_type[sizeof(sa->salg_type) - 1] = 0;
 | |
| 	sa->salg_name[sizeof(sa->salg_name) - 1] = 0;
 | |
| 
 | |
| 	type = alg_get_type(sa->salg_type);
 | |
| 	if (IS_ERR(type) && PTR_ERR(type) == -ENOENT) {
 | |
| 		request_module("algif-%s", sa->salg_type);
 | |
| 		type = alg_get_type(sa->salg_type);
 | |
| 	}
 | |
| 
 | |
| 	if (IS_ERR(type))
 | |
| 		return PTR_ERR(type);
 | |
| 
 | |
| 	private = type->bind(sa->salg_name, sa->salg_feat, sa->salg_mask);
 | |
| 	if (IS_ERR(private)) {
 | |
| 		module_put(type->owner);
 | |
| 		return PTR_ERR(private);
 | |
| 	}
 | |
| 
 | |
| 	lock_sock(sk);
 | |
| 
 | |
| 	swap(ask->type, type);
 | |
| 	swap(ask->private, private);
 | |
| 
 | |
| 	release_sock(sk);
 | |
| 
 | |
| 	alg_do_release(type, private);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int alg_setkey(struct sock *sk, char __user *ukey,
 | |
| 		      unsigned int keylen)
 | |
| {
 | |
| 	struct alg_sock *ask = alg_sk(sk);
 | |
| 	const struct af_alg_type *type = ask->type;
 | |
| 	u8 *key;
 | |
| 	int err;
 | |
| 
 | |
| 	key = sock_kmalloc(sk, keylen, GFP_KERNEL);
 | |
| 	if (!key)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	err = -EFAULT;
 | |
| 	if (copy_from_user(key, ukey, keylen))
 | |
| 		goto out;
 | |
| 
 | |
| 	err = type->setkey(ask->private, key, keylen);
 | |
| 
 | |
| out:
 | |
| 	sock_kfree_s(sk, key, keylen);
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static int alg_setsockopt(struct socket *sock, int level, int optname,
 | |
| 			  char __user *optval, unsigned int optlen)
 | |
| {
 | |
| 	struct sock *sk = sock->sk;
 | |
| 	struct alg_sock *ask = alg_sk(sk);
 | |
| 	const struct af_alg_type *type;
 | |
| 	int err = -ENOPROTOOPT;
 | |
| 
 | |
| 	lock_sock(sk);
 | |
| 	type = ask->type;
 | |
| 
 | |
| 	if (level != SOL_ALG || !type)
 | |
| 		goto unlock;
 | |
| 
 | |
| 	switch (optname) {
 | |
| 	case ALG_SET_KEY:
 | |
| 		if (sock->state == SS_CONNECTED)
 | |
| 			goto unlock;
 | |
| 		if (!type->setkey)
 | |
| 			goto unlock;
 | |
| 
 | |
| 		err = alg_setkey(sk, optval, optlen);
 | |
| 	}
 | |
| 
 | |
| unlock:
 | |
| 	release_sock(sk);
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| int af_alg_accept(struct sock *sk, struct socket *newsock)
 | |
| {
 | |
| 	struct alg_sock *ask = alg_sk(sk);
 | |
| 	const struct af_alg_type *type;
 | |
| 	struct sock *sk2;
 | |
| 	int err;
 | |
| 
 | |
| 	lock_sock(sk);
 | |
| 	type = ask->type;
 | |
| 
 | |
| 	err = -EINVAL;
 | |
| 	if (!type)
 | |
| 		goto unlock;
 | |
| 
 | |
| 	sk2 = sk_alloc(sock_net(sk), PF_ALG, GFP_KERNEL, &alg_proto);
 | |
| 	err = -ENOMEM;
 | |
| 	if (!sk2)
 | |
| 		goto unlock;
 | |
| 
 | |
| 	sock_init_data(newsock, sk2);
 | |
| 	sock_graft(sk2, newsock);
 | |
| 
 | |
| 	err = type->accept(ask->private, sk2);
 | |
| 	if (err) {
 | |
| 		sk_free(sk2);
 | |
| 		goto unlock;
 | |
| 	}
 | |
| 
 | |
| 	sk2->sk_family = PF_ALG;
 | |
| 
 | |
| 	sock_hold(sk);
 | |
| 	alg_sk(sk2)->parent = sk;
 | |
| 	alg_sk(sk2)->type = type;
 | |
| 
 | |
| 	newsock->ops = type->ops;
 | |
| 	newsock->state = SS_CONNECTED;
 | |
| 
 | |
| 	err = 0;
 | |
| 
 | |
| unlock:
 | |
| 	release_sock(sk);
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(af_alg_accept);
 | |
| 
 | |
| static int alg_accept(struct socket *sock, struct socket *newsock, int flags)
 | |
| {
 | |
| 	return af_alg_accept(sock->sk, newsock);
 | |
| }
 | |
| 
 | |
| static const struct proto_ops alg_proto_ops = {
 | |
| 	.family		=	PF_ALG,
 | |
| 	.owner		=	THIS_MODULE,
 | |
| 
 | |
| 	.connect	=	sock_no_connect,
 | |
| 	.socketpair	=	sock_no_socketpair,
 | |
| 	.getname	=	sock_no_getname,
 | |
| 	.ioctl		=	sock_no_ioctl,
 | |
| 	.listen		=	sock_no_listen,
 | |
| 	.shutdown	=	sock_no_shutdown,
 | |
| 	.getsockopt	=	sock_no_getsockopt,
 | |
| 	.mmap		=	sock_no_mmap,
 | |
| 	.sendpage	=	sock_no_sendpage,
 | |
| 	.sendmsg	=	sock_no_sendmsg,
 | |
| 	.recvmsg	=	sock_no_recvmsg,
 | |
| 	.poll		=	sock_no_poll,
 | |
| 
 | |
| 	.bind		=	alg_bind,
 | |
| 	.release	=	af_alg_release,
 | |
| 	.setsockopt	=	alg_setsockopt,
 | |
| 	.accept		=	alg_accept,
 | |
| };
 | |
| 
 | |
| static void alg_sock_destruct(struct sock *sk)
 | |
| {
 | |
| 	struct alg_sock *ask = alg_sk(sk);
 | |
| 
 | |
| 	alg_do_release(ask->type, ask->private);
 | |
| }
 | |
| 
 | |
| static int alg_create(struct net *net, struct socket *sock, int protocol,
 | |
| 		      int kern)
 | |
| {
 | |
| 	struct sock *sk;
 | |
| 	int err;
 | |
| 
 | |
| 	if (sock->type != SOCK_SEQPACKET)
 | |
| 		return -ESOCKTNOSUPPORT;
 | |
| 	if (protocol != 0)
 | |
| 		return -EPROTONOSUPPORT;
 | |
| 
 | |
| 	err = -ENOMEM;
 | |
| 	sk = sk_alloc(net, PF_ALG, GFP_KERNEL, &alg_proto);
 | |
| 	if (!sk)
 | |
| 		goto out;
 | |
| 
 | |
| 	sock->ops = &alg_proto_ops;
 | |
| 	sock_init_data(sock, sk);
 | |
| 
 | |
| 	sk->sk_family = PF_ALG;
 | |
| 	sk->sk_destruct = alg_sock_destruct;
 | |
| 
 | |
| 	return 0;
 | |
| out:
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static const struct net_proto_family alg_family = {
 | |
| 	.family	=	PF_ALG,
 | |
| 	.create	=	alg_create,
 | |
| 	.owner	=	THIS_MODULE,
 | |
| };
 | |
| 
 | |
| int af_alg_make_sg(struct af_alg_sgl *sgl, void __user *addr, int len,
 | |
| 		   int write)
 | |
| {
 | |
| 	unsigned long from = (unsigned long)addr;
 | |
| 	unsigned long npages;
 | |
| 	unsigned off;
 | |
| 	int err;
 | |
| 	int i;
 | |
| 
 | |
| 	err = -EFAULT;
 | |
| 	if (!access_ok(write ? VERIFY_READ : VERIFY_WRITE, addr, len))
 | |
| 		goto out;
 | |
| 
 | |
| 	off = from & ~PAGE_MASK;
 | |
| 	npages = (off + len + PAGE_SIZE - 1) >> PAGE_SHIFT;
 | |
| 	if (npages > ALG_MAX_PAGES)
 | |
| 		npages = ALG_MAX_PAGES;
 | |
| 
 | |
| 	err = get_user_pages_fast(from, npages, write, sgl->pages);
 | |
| 	if (err < 0)
 | |
| 		goto out;
 | |
| 
 | |
| 	npages = err;
 | |
| 	err = -EINVAL;
 | |
| 	if (WARN_ON(npages == 0))
 | |
| 		goto out;
 | |
| 
 | |
| 	err = 0;
 | |
| 
 | |
| 	sg_init_table(sgl->sg, npages);
 | |
| 
 | |
| 	for (i = 0; i < npages; i++) {
 | |
| 		int plen = min_t(int, len, PAGE_SIZE - off);
 | |
| 
 | |
| 		sg_set_page(sgl->sg + i, sgl->pages[i], plen, off);
 | |
| 
 | |
| 		off = 0;
 | |
| 		len -= plen;
 | |
| 		err += plen;
 | |
| 	}
 | |
| 
 | |
| out:
 | |
| 	return err;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(af_alg_make_sg);
 | |
| 
 | |
| void af_alg_free_sg(struct af_alg_sgl *sgl)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	i = 0;
 | |
| 	do {
 | |
| 		put_page(sgl->pages[i]);
 | |
| 	} while (!sg_is_last(sgl->sg + (i++)));
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(af_alg_free_sg);
 | |
| 
 | |
| int af_alg_cmsg_send(struct msghdr *msg, struct af_alg_control *con)
 | |
| {
 | |
| 	struct cmsghdr *cmsg;
 | |
| 
 | |
| 	for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
 | |
| 		if (!CMSG_OK(msg, cmsg))
 | |
| 			return -EINVAL;
 | |
| 		if (cmsg->cmsg_level != SOL_ALG)
 | |
| 			continue;
 | |
| 
 | |
| 		switch(cmsg->cmsg_type) {
 | |
| 		case ALG_SET_IV:
 | |
| 			if (cmsg->cmsg_len < CMSG_LEN(sizeof(*con->iv)))
 | |
| 				return -EINVAL;
 | |
| 			con->iv = (void *)CMSG_DATA(cmsg);
 | |
| 			if (cmsg->cmsg_len < CMSG_LEN(con->iv->ivlen +
 | |
| 						      sizeof(*con->iv)))
 | |
| 				return -EINVAL;
 | |
| 			break;
 | |
| 
 | |
| 		case ALG_SET_OP:
 | |
| 			if (cmsg->cmsg_len < CMSG_LEN(sizeof(u32)))
 | |
| 				return -EINVAL;
 | |
| 			con->op = *(u32 *)CMSG_DATA(cmsg);
 | |
| 			break;
 | |
| 
 | |
| 		default:
 | |
| 			return -EINVAL;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(af_alg_cmsg_send);
 | |
| 
 | |
| int af_alg_wait_for_completion(int err, struct af_alg_completion *completion)
 | |
| {
 | |
| 	switch (err) {
 | |
| 	case -EINPROGRESS:
 | |
| 	case -EBUSY:
 | |
| 		wait_for_completion(&completion->completion);
 | |
| 		INIT_COMPLETION(completion->completion);
 | |
| 		err = completion->err;
 | |
| 		break;
 | |
| 	};
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(af_alg_wait_for_completion);
 | |
| 
 | |
| void af_alg_complete(struct crypto_async_request *req, int err)
 | |
| {
 | |
| 	struct af_alg_completion *completion = req->data;
 | |
| 
 | |
| 	completion->err = err;
 | |
| 	complete(&completion->completion);
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(af_alg_complete);
 | |
| 
 | |
| static int __init af_alg_init(void)
 | |
| {
 | |
| 	int err = proto_register(&alg_proto, 0);
 | |
| 
 | |
| 	if (err)
 | |
| 		goto out;
 | |
| 
 | |
| 	err = sock_register(&alg_family);
 | |
| 	if (err != 0)
 | |
| 		goto out_unregister_proto;
 | |
| 
 | |
| out:
 | |
| 	return err;
 | |
| 
 | |
| out_unregister_proto:
 | |
| 	proto_unregister(&alg_proto);
 | |
| 	goto out;
 | |
| }
 | |
| 
 | |
| static void __exit af_alg_exit(void)
 | |
| {
 | |
| 	sock_unregister(PF_ALG);
 | |
| 	proto_unregister(&alg_proto);
 | |
| }
 | |
| 
 | |
| module_init(af_alg_init);
 | |
| module_exit(af_alg_exit);
 | |
| MODULE_LICENSE("GPL");
 | |
| MODULE_ALIAS_NETPROTO(AF_ALG);
 |