aboutsummaryrefslogtreecommitdiffstats
path: root/crypto/af_alg.c
diff options
context:
space:
mode:
authorHerbert Xu <herbert@gondor.apana.org.au>2010-10-19 09:12:39 -0400
committerHerbert Xu <herbert@gondor.apana.org.au>2010-11-19 04:47:57 -0500
commit03c8efc1ffeb6b82a22c1af8dd908af349563314 (patch)
treea2538f6c5151ca92aadac3d52d9703d39d254584 /crypto/af_alg.c
parentc2f9bff5ace07fbea03a53c6c3253f6c3a81e9f9 (diff)
crypto: af_alg - User-space interface for Crypto API
This patch creates the backbone of the user-space interface for the Crypto API, through a new socket family AF_ALG. Each session corresponds to one or more connections obtained from that socket. The number depends on the number of inputs/outputs of that particular type of operation. For most types there will be a s ingle connection/file descriptor that is used for both input and output. AEAD is one of the few that require two inputs. Each algorithm type will provide its own implementation that plugs into af_alg. They're keyed using a string such as "skcipher" or "hash". IOW this patch only contains the boring bits that is required to hold everything together. Thakns to Miloslav Trmac for reviewing this and contributing fixes and improvements. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Acked-by: David S. Miller <davem@davemloft.net> Tested-by: Martin Willi <martin@strongswan.org>
Diffstat (limited to 'crypto/af_alg.c')
-rw-r--r--crypto/af_alg.c482
1 files changed, 482 insertions, 0 deletions
diff --git a/crypto/af_alg.c b/crypto/af_alg.c
new file mode 100644
index 000000000000..cabed0e9c9d8
--- /dev/null
+++ b/crypto/af_alg.c
@@ -0,0 +1,482 @@
1/*
2 * af_alg: User-space algorithm interface
3 *
4 * This file provides the user-space API for algorithms.
5 *
6 * Copyright (c) 2010 Herbert Xu <herbert@gondor.apana.org.au>
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the Free
10 * Software Foundation; either version 2 of the License, or (at your option)
11 * any later version.
12 *
13 */
14
15#include <asm/atomic.h>
16#include <crypto/if_alg.h>
17#include <linux/crypto.h>
18#include <linux/init.h>
19#include <linux/kernel.h>
20#include <linux/list.h>
21#include <linux/module.h>
22#include <linux/net.h>
23#include <linux/rwsem.h>
24
25struct alg_type_list {
26 const struct af_alg_type *type;
27 struct list_head list;
28};
29
30static atomic_t alg_memory_allocated;
31
32static struct proto alg_proto = {
33 .name = "ALG",
34 .owner = THIS_MODULE,
35 .memory_allocated = &alg_memory_allocated,
36 .obj_size = sizeof(struct alg_sock),
37};
38
39static LIST_HEAD(alg_types);
40static DECLARE_RWSEM(alg_types_sem);
41
42static const struct af_alg_type *alg_get_type(const char *name)
43{
44 const struct af_alg_type *type = ERR_PTR(-ENOENT);
45 struct alg_type_list *node;
46
47 down_read(&alg_types_sem);
48 list_for_each_entry(node, &alg_types, list) {
49 if (strcmp(node->type->name, name))
50 continue;
51
52 if (try_module_get(node->type->owner))
53 type = node->type;
54 break;
55 }
56 up_read(&alg_types_sem);
57
58 return type;
59}
60
61int af_alg_register_type(const struct af_alg_type *type)
62{
63 struct alg_type_list *node;
64 int err = -EEXIST;
65
66 down_write(&alg_types_sem);
67 list_for_each_entry(node, &alg_types, list) {
68 if (!strcmp(node->type->name, type->name))
69 goto unlock;
70 }
71
72 node = kmalloc(sizeof(*node), GFP_KERNEL);
73 err = -ENOMEM;
74 if (!node)
75 goto unlock;
76
77 type->ops->owner = THIS_MODULE;
78 node->type = type;
79 list_add(&node->list, &alg_types);
80 err = 0;
81
82unlock:
83 up_write(&alg_types_sem);
84
85 return err;
86}
87EXPORT_SYMBOL_GPL(af_alg_register_type);
88
89int af_alg_unregister_type(const struct af_alg_type *type)
90{
91 struct alg_type_list *node;
92 int err = -ENOENT;
93
94 down_write(&alg_types_sem);
95 list_for_each_entry(node, &alg_types, list) {
96 if (strcmp(node->type->name, type->name))
97 continue;
98
99 list_del(&node->list);
100 kfree(node);
101 err = 0;
102 break;
103 }
104 up_write(&alg_types_sem);
105
106 return err;
107}
108EXPORT_SYMBOL_GPL(af_alg_unregister_type);
109
110static void alg_do_release(const struct af_alg_type *type, void *private)
111{
112 if (!type)
113 return;
114
115 type->release(private);
116 module_put(type->owner);
117}
118
119int af_alg_release(struct socket *sock)
120{
121 if (sock->sk)
122 sock_put(sock->sk);
123 return 0;
124}
125EXPORT_SYMBOL_GPL(af_alg_release);
126
127static int alg_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
128{
129 struct sock *sk = sock->sk;
130 struct alg_sock *ask = alg_sk(sk);
131 struct sockaddr_alg *sa = (void *)uaddr;
132 const struct af_alg_type *type;
133 void *private;
134
135 if (sock->state == SS_CONNECTED)
136 return -EINVAL;
137
138 if (addr_len != sizeof(*sa))
139 return -EINVAL;
140
141 sa->salg_type[sizeof(sa->salg_type) - 1] = 0;
142 sa->salg_name[sizeof(sa->salg_name) - 1] = 0;
143
144 type = alg_get_type(sa->salg_type);
145 if (IS_ERR(type) && PTR_ERR(type) == -ENOENT) {
146 request_module("algif-%s", sa->salg_type);
147 type = alg_get_type(sa->salg_type);
148 }
149
150 if (IS_ERR(type))
151 return PTR_ERR(type);
152
153 private = type->bind(sa->salg_name, sa->salg_feat, sa->salg_mask);
154 if (IS_ERR(private)) {
155 module_put(type->owner);
156 return PTR_ERR(private);
157 }
158
159 lock_sock(sk);
160
161 swap(ask->type, type);
162 swap(ask->private, private);
163
164 release_sock(sk);
165
166 alg_do_release(type, private);
167
168 return 0;
169}
170
171static int alg_setkey(struct sock *sk, char __user *ukey,
172 unsigned int keylen)
173{
174 struct alg_sock *ask = alg_sk(sk);
175 const struct af_alg_type *type = ask->type;
176 u8 *key;
177 int err;
178
179 key = sock_kmalloc(sk, keylen, GFP_KERNEL);
180 if (!key)
181 return -ENOMEM;
182
183 err = -EFAULT;
184 if (copy_from_user(key, ukey, keylen))
185 goto out;
186
187 err = type->setkey(ask->private, key, keylen);
188
189out:
190 sock_kfree_s(sk, key, keylen);
191
192 return err;
193}
194
195static int alg_setsockopt(struct socket *sock, int level, int optname,
196 char __user *optval, unsigned int optlen)
197{
198 struct sock *sk = sock->sk;
199 struct alg_sock *ask = alg_sk(sk);
200 const struct af_alg_type *type;
201 int err = -ENOPROTOOPT;
202
203 lock_sock(sk);
204 type = ask->type;
205
206 if (level != SOL_ALG || !type)
207 goto unlock;
208
209 switch (optname) {
210 case ALG_SET_KEY:
211 if (sock->state == SS_CONNECTED)
212 goto unlock;
213 if (!type->setkey)
214 goto unlock;
215
216 err = alg_setkey(sk, optval, optlen);
217 }
218
219unlock:
220 release_sock(sk);
221
222 return err;
223}
224
225int af_alg_accept(struct sock *sk, struct socket *newsock)
226{
227 struct alg_sock *ask = alg_sk(sk);
228 const struct af_alg_type *type;
229 struct sock *sk2;
230 int err;
231
232 lock_sock(sk);
233 type = ask->type;
234
235 err = -EINVAL;
236 if (!type)
237 goto unlock;
238
239 sk2 = sk_alloc(sock_net(sk), PF_ALG, GFP_KERNEL, &alg_proto);
240 err = -ENOMEM;
241 if (!sk2)
242 goto unlock;
243
244 sock_init_data(newsock, sk2);
245
246 err = type->accept(ask->private, sk2);
247 if (err) {
248 sk_free(sk2);
249 goto unlock;
250 }
251
252 sk2->sk_family = PF_ALG;
253
254 sock_hold(sk);
255 alg_sk(sk2)->parent = sk;
256 alg_sk(sk2)->type = type;
257
258 newsock->ops = type->ops;
259 newsock->state = SS_CONNECTED;
260
261 err = 0;
262
263unlock:
264 release_sock(sk);
265
266 return err;
267}
268EXPORT_SYMBOL_GPL(af_alg_accept);
269
270static int alg_accept(struct socket *sock, struct socket *newsock, int flags)
271{
272 return af_alg_accept(sock->sk, newsock);
273}
274
275static const struct proto_ops alg_proto_ops = {
276 .family = PF_ALG,
277 .owner = THIS_MODULE,
278
279 .connect = sock_no_connect,
280 .socketpair = sock_no_socketpair,
281 .getname = sock_no_getname,
282 .ioctl = sock_no_ioctl,
283 .listen = sock_no_listen,
284 .shutdown = sock_no_shutdown,
285 .getsockopt = sock_no_getsockopt,
286 .mmap = sock_no_mmap,
287 .sendpage = sock_no_sendpage,
288 .sendmsg = sock_no_sendmsg,
289 .recvmsg = sock_no_recvmsg,
290 .poll = sock_no_poll,
291
292 .bind = alg_bind,
293 .release = af_alg_release,
294 .setsockopt = alg_setsockopt,
295 .accept = alg_accept,
296};
297
298static void alg_sock_destruct(struct sock *sk)
299{
300 struct alg_sock *ask = alg_sk(sk);
301
302 alg_do_release(ask->type, ask->private);
303}
304
305static int alg_create(struct net *net, struct socket *sock, int protocol,
306 int kern)
307{
308 struct sock *sk;
309 int err;
310
311 if (sock->type != SOCK_SEQPACKET)
312 return -ESOCKTNOSUPPORT;
313 if (protocol != 0)
314 return -EPROTONOSUPPORT;
315
316 err = -ENOMEM;
317 sk = sk_alloc(net, PF_ALG, GFP_KERNEL, &alg_proto);
318 if (!sk)
319 goto out;
320
321 sock->ops = &alg_proto_ops;
322 sock_init_data(sock, sk);
323
324 sk->sk_family = PF_ALG;
325 sk->sk_destruct = alg_sock_destruct;
326
327 return 0;
328out:
329 return err;
330}
331
332static const struct net_proto_family alg_family = {
333 .family = PF_ALG,
334 .create = alg_create,
335 .owner = THIS_MODULE,
336};
337
338int af_alg_make_sg(struct af_alg_sgl *sgl, void __user *addr, int len,
339 int write)
340{
341 unsigned long from = (unsigned long)addr;
342 unsigned long npages;
343 unsigned off;
344 int err;
345 int i;
346
347 err = -EFAULT;
348 if (!access_ok(write ? VERIFY_READ : VERIFY_WRITE, addr, len))
349 goto out;
350
351 off = from & ~PAGE_MASK;
352 npages = (off + len + PAGE_SIZE - 1) >> PAGE_SHIFT;
353 if (npages > ALG_MAX_PAGES)
354 npages = ALG_MAX_PAGES;
355
356 err = get_user_pages_fast(from, npages, write, sgl->pages);
357 if (err < 0)
358 goto out;
359
360 npages = err;
361 err = -EINVAL;
362 if (WARN_ON(npages == 0))
363 goto out;
364
365 err = 0;
366
367 sg_init_table(sgl->sg, npages);
368
369 for (i = 0; i < npages; i++) {
370 int plen = min_t(int, len, PAGE_SIZE - off);
371
372 sg_set_page(sgl->sg + i, sgl->pages[i], plen, off);
373
374 off = 0;
375 len -= plen;
376 err += plen;
377 }
378
379out:
380 return err;
381}
382EXPORT_SYMBOL_GPL(af_alg_make_sg);
383
384void af_alg_free_sg(struct af_alg_sgl *sgl)
385{
386 int i;
387
388 i = 0;
389 do {
390 put_page(sgl->pages[i]);
391 } while (!sg_is_last(sgl->sg + (i++)));
392}
393EXPORT_SYMBOL_GPL(af_alg_free_sg);
394
395int af_alg_cmsg_send(struct msghdr *msg, struct af_alg_control *con)
396{
397 struct cmsghdr *cmsg;
398
399 for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
400 if (!CMSG_OK(msg, cmsg))
401 return -EINVAL;
402 if (cmsg->cmsg_level != SOL_ALG)
403 continue;
404
405 switch(cmsg->cmsg_type) {
406 case ALG_SET_IV:
407 if (cmsg->cmsg_len < CMSG_LEN(sizeof(*con->iv)))
408 return -EINVAL;
409 con->iv = (void *)CMSG_DATA(cmsg);
410 if (cmsg->cmsg_len < CMSG_LEN(con->iv->ivlen +
411 sizeof(*con->iv)))
412 return -EINVAL;
413 break;
414
415 case ALG_SET_OP:
416 if (cmsg->cmsg_len < CMSG_LEN(sizeof(u32)))
417 return -EINVAL;
418 con->op = *(u32 *)CMSG_DATA(cmsg);
419 break;
420
421 default:
422 return -EINVAL;
423 }
424 }
425
426 return 0;
427}
428EXPORT_SYMBOL_GPL(af_alg_cmsg_send);
429
430int af_alg_wait_for_completion(int err, struct af_alg_completion *completion)
431{
432 switch (err) {
433 case -EINPROGRESS:
434 case -EBUSY:
435 wait_for_completion(&completion->completion);
436 INIT_COMPLETION(completion->completion);
437 err = completion->err;
438 break;
439 };
440
441 return err;
442}
443EXPORT_SYMBOL_GPL(af_alg_wait_for_completion);
444
445void af_alg_complete(struct crypto_async_request *req, int err)
446{
447 struct af_alg_completion *completion = req->data;
448
449 completion->err = err;
450 complete(&completion->completion);
451}
452EXPORT_SYMBOL_GPL(af_alg_complete);
453
454static int __init af_alg_init(void)
455{
456 int err = proto_register(&alg_proto, 0);
457
458 if (err)
459 goto out;
460
461 err = sock_register(&alg_family);
462 if (err != 0)
463 goto out_unregister_proto;
464
465out:
466 return err;
467
468out_unregister_proto:
469 proto_unregister(&alg_proto);
470 goto out;
471}
472
473static void __exit af_alg_exit(void)
474{
475 sock_unregister(PF_ALG);
476 proto_unregister(&alg_proto);
477}
478
479module_init(af_alg_init);
480module_exit(af_alg_exit);
481MODULE_LICENSE("GPL");
482MODULE_ALIAS_NETPROTO(AF_ALG);