| /* |
| * 2007+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net> |
| * All rights reserved. |
| * |
| * 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. |
| */ |
| |
| #include <linux/bio.h> |
| #include <linux/crypto.h> |
| #include <linux/dst.h> |
| #include <linux/kernel.h> |
| #include <linux/scatterlist.h> |
| #include <linux/slab.h> |
| |
| /* |
| * Tricky bastard, but IV can be more complex with time... |
| */ |
| static inline u64 dst_gen_iv(struct dst_trans *t) |
| { |
| return t->gen; |
| } |
| |
| /* |
| * Crypto machinery: hash/cipher support for the given crypto controls. |
| */ |
| static struct crypto_hash *dst_init_hash(struct dst_crypto_ctl *ctl, u8 *key) |
| { |
| int err; |
| struct crypto_hash *hash; |
| |
| hash = crypto_alloc_hash(ctl->hash_algo, 0, CRYPTO_ALG_ASYNC); |
| if (IS_ERR(hash)) { |
| err = PTR_ERR(hash); |
| dprintk("%s: failed to allocate hash '%s', err: %d.\n", |
| __func__, ctl->hash_algo, err); |
| goto err_out_exit; |
| } |
| |
| ctl->crypto_attached_size = crypto_hash_digestsize(hash); |
| |
| if (!ctl->hash_keysize) |
| return hash; |
| |
| err = crypto_hash_setkey(hash, key, ctl->hash_keysize); |
| if (err) { |
| dprintk("%s: failed to set key for hash '%s', err: %d.\n", |
| __func__, ctl->hash_algo, err); |
| goto err_out_free; |
| } |
| |
| return hash; |
| |
| err_out_free: |
| crypto_free_hash(hash); |
| err_out_exit: |
| return ERR_PTR(err); |
| } |
| |
| static struct crypto_ablkcipher *dst_init_cipher(struct dst_crypto_ctl *ctl, |
| u8 *key) |
| { |
| int err = -EINVAL; |
| struct crypto_ablkcipher *cipher; |
| |
| if (!ctl->cipher_keysize) |
| goto err_out_exit; |
| |
| cipher = crypto_alloc_ablkcipher(ctl->cipher_algo, 0, 0); |
| if (IS_ERR(cipher)) { |
| err = PTR_ERR(cipher); |
| dprintk("%s: failed to allocate cipher '%s', err: %d.\n", |
| __func__, ctl->cipher_algo, err); |
| goto err_out_exit; |
| } |
| |
| crypto_ablkcipher_clear_flags(cipher, ~0); |
| |
| err = crypto_ablkcipher_setkey(cipher, key, ctl->cipher_keysize); |
| if (err) { |
| dprintk("%s: failed to set key for cipher '%s', err: %d.\n", |
| __func__, ctl->cipher_algo, err); |
| goto err_out_free; |
| } |
| |
| return cipher; |
| |
| err_out_free: |
| crypto_free_ablkcipher(cipher); |
| err_out_exit: |
| return ERR_PTR(err); |
| } |
| |
| /* |
| * Crypto engine has a pool of pages to encrypt data into before sending |
| * it over the network. This pool is freed/allocated here. |
| */ |
| static void dst_crypto_pages_free(struct dst_crypto_engine *e) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < e->page_num; ++i) |
| __free_page(e->pages[i]); |
| kfree(e->pages); |
| } |
| |
| static int dst_crypto_pages_alloc(struct dst_crypto_engine *e, int num) |
| { |
| int i; |
| |
| e->pages = kmalloc(num * sizeof(struct page **), GFP_KERNEL); |
| if (!e->pages) |
| return -ENOMEM; |
| |
| for (i = 0; i < num; ++i) { |
| e->pages[i] = alloc_page(GFP_KERNEL); |
| if (!e->pages[i]) |
| goto err_out_free_pages; |
| } |
| |
| e->page_num = num; |
| return 0; |
| |
| err_out_free_pages: |
| while (--i >= 0) |
| __free_page(e->pages[i]); |
| |
| kfree(e->pages); |
| return -ENOMEM; |
| } |
| |
| /* |
| * Initialize crypto engine for given node. |
| * Setup cipher/hash, keys, pool of threads and private data. |
| */ |
| static int dst_crypto_engine_init(struct dst_crypto_engine *e, |
| struct dst_node *n) |
| { |
| int err; |
| struct dst_crypto_ctl *ctl = &n->crypto; |
| |
| err = dst_crypto_pages_alloc(e, n->max_pages); |
| if (err) |
| goto err_out_exit; |
| |
| e->size = PAGE_SIZE; |
| e->data = kmalloc(e->size, GFP_KERNEL); |
| if (!e->data) { |
| err = -ENOMEM; |
| goto err_out_free_pages; |
| } |
| |
| if (ctl->hash_algo[0]) { |
| e->hash = dst_init_hash(ctl, n->hash_key); |
| if (IS_ERR(e->hash)) { |
| err = PTR_ERR(e->hash); |
| e->hash = NULL; |
| goto err_out_free; |
| } |
| } |
| |
| if (ctl->cipher_algo[0]) { |
| e->cipher = dst_init_cipher(ctl, n->cipher_key); |
| if (IS_ERR(e->cipher)) { |
| err = PTR_ERR(e->cipher); |
| e->cipher = NULL; |
| goto err_out_free_hash; |
| } |
| } |
| |
| return 0; |
| |
| err_out_free_hash: |
| crypto_free_hash(e->hash); |
| err_out_free: |
| kfree(e->data); |
| err_out_free_pages: |
| dst_crypto_pages_free(e); |
| err_out_exit: |
| return err; |
| } |
| |
| static void dst_crypto_engine_exit(struct dst_crypto_engine *e) |
| { |
| if (e->hash) |
| crypto_free_hash(e->hash); |
| if (e->cipher) |
| crypto_free_ablkcipher(e->cipher); |
| dst_crypto_pages_free(e); |
| kfree(e->data); |
| } |
| |
| /* |
| * Waiting for cipher processing to be completed. |
| */ |
| struct dst_crypto_completion { |
| struct completion complete; |
| int error; |
| }; |
| |
| static void dst_crypto_complete(struct crypto_async_request *req, int err) |
| { |
| struct dst_crypto_completion *c = req->data; |
| |
| if (err == -EINPROGRESS) |
| return; |
| |
| dprintk("%s: req: %p, err: %d.\n", __func__, req, err); |
| c->error = err; |
| complete(&c->complete); |
| } |
| |
| static int dst_crypto_process(struct ablkcipher_request *req, |
| struct scatterlist *sg_dst, struct scatterlist *sg_src, |
| void *iv, int enc, unsigned long timeout) |
| { |
| struct dst_crypto_completion c; |
| int err; |
| |
| init_completion(&c.complete); |
| c.error = -EINPROGRESS; |
| |
| ablkcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, |
| dst_crypto_complete, &c); |
| |
| ablkcipher_request_set_crypt(req, sg_src, sg_dst, sg_src->length, iv); |
| |
| if (enc) |
| err = crypto_ablkcipher_encrypt(req); |
| else |
| err = crypto_ablkcipher_decrypt(req); |
| |
| switch (err) { |
| case -EINPROGRESS: |
| case -EBUSY: |
| err = wait_for_completion_interruptible_timeout(&c.complete, |
| timeout); |
| if (!err) |
| err = -ETIMEDOUT; |
| else |
| err = c.error; |
| break; |
| default: |
| break; |
| } |
| |
| return err; |
| } |
| |
| /* |
| * DST uses generic iteration approach for data crypto processing. |
| * Single block IO request is switched into array of scatterlists, |
| * which are submitted to the crypto processing iterator. |
| * |
| * Input and output iterator initialization are different, since |
| * in output case we can not encrypt data in-place and need a |
| * temporary storage, which is then being sent to the remote peer. |
| */ |
| static int dst_trans_iter_out(struct bio *bio, struct dst_crypto_engine *e, |
| int (*iterator) (struct dst_crypto_engine *e, |
| struct scatterlist *dst, |
| struct scatterlist *src)) |
| { |
| struct bio_vec *bv; |
| int err, i; |
| |
| sg_init_table(e->src, bio->bi_vcnt); |
| sg_init_table(e->dst, bio->bi_vcnt); |
| |
| bio_for_each_segment(bv, bio, i) { |
| sg_set_page(&e->src[i], bv->bv_page, bv->bv_len, bv->bv_offset); |
| sg_set_page(&e->dst[i], e->pages[i], bv->bv_len, bv->bv_offset); |
| |
| err = iterator(e, &e->dst[i], &e->src[i]); |
| if (err) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static int dst_trans_iter_in(struct bio *bio, struct dst_crypto_engine *e, |
| int (*iterator) (struct dst_crypto_engine *e, |
| struct scatterlist *dst, |
| struct scatterlist *src)) |
| { |
| struct bio_vec *bv; |
| int err, i; |
| |
| sg_init_table(e->src, bio->bi_vcnt); |
| sg_init_table(e->dst, bio->bi_vcnt); |
| |
| bio_for_each_segment(bv, bio, i) { |
| sg_set_page(&e->src[i], bv->bv_page, bv->bv_len, bv->bv_offset); |
| sg_set_page(&e->dst[i], bv->bv_page, bv->bv_len, bv->bv_offset); |
| |
| err = iterator(e, &e->dst[i], &e->src[i]); |
| if (err) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static int dst_crypt_iterator(struct dst_crypto_engine *e, |
| struct scatterlist *sg_dst, struct scatterlist *sg_src) |
| { |
| struct ablkcipher_request *req = e->data; |
| u8 iv[32]; |
| |
| memset(iv, 0, sizeof(iv)); |
| |
| memcpy(iv, &e->iv, sizeof(e->iv)); |
| |
| return dst_crypto_process(req, sg_dst, sg_src, iv, e->enc, e->timeout); |
| } |
| |
| static int dst_crypt(struct dst_crypto_engine *e, struct bio *bio) |
| { |
| struct ablkcipher_request *req = e->data; |
| |
| memset(req, 0, sizeof(struct ablkcipher_request)); |
| ablkcipher_request_set_tfm(req, e->cipher); |
| |
| if (e->enc) |
| return dst_trans_iter_out(bio, e, dst_crypt_iterator); |
| else |
| return dst_trans_iter_in(bio, e, dst_crypt_iterator); |
| } |
| |
| static int dst_hash_iterator(struct dst_crypto_engine *e, |
| struct scatterlist *sg_dst, struct scatterlist *sg_src) |
| { |
| return crypto_hash_update(e->data, sg_src, sg_src->length); |
| } |
| |
| static int dst_hash(struct dst_crypto_engine *e, struct bio *bio, void *dst) |
| { |
| struct hash_desc *desc = e->data; |
| int err; |
| |
| desc->tfm = e->hash; |
| desc->flags = 0; |
| |
| err = crypto_hash_init(desc); |
| if (err) |
| return err; |
| |
| err = dst_trans_iter_in(bio, e, dst_hash_iterator); |
| if (err) |
| return err; |
| |
| err = crypto_hash_final(desc, dst); |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| /* |
| * Initialize/cleanup a crypto thread. The only thing it should |
| * do is to allocate a pool of pages as temporary storage. |
| * And to setup cipher and/or hash. |
| */ |
| static void *dst_crypto_thread_init(void *data) |
| { |
| struct dst_node *n = data; |
| struct dst_crypto_engine *e; |
| int err = -ENOMEM; |
| |
| e = kzalloc(sizeof(struct dst_crypto_engine), GFP_KERNEL); |
| if (!e) |
| goto err_out_exit; |
| e->src = kcalloc(2 * n->max_pages, sizeof(struct scatterlist), |
| GFP_KERNEL); |
| if (!e->src) |
| goto err_out_free; |
| |
| e->dst = e->src + n->max_pages; |
| |
| err = dst_crypto_engine_init(e, n); |
| if (err) |
| goto err_out_free_all; |
| |
| return e; |
| |
| err_out_free_all: |
| kfree(e->src); |
| err_out_free: |
| kfree(e); |
| err_out_exit: |
| return ERR_PTR(err); |
| } |
| |
| static void dst_crypto_thread_cleanup(void *private) |
| { |
| struct dst_crypto_engine *e = private; |
| |
| dst_crypto_engine_exit(e); |
| kfree(e->src); |
| kfree(e); |
| } |
| |
| /* |
| * Initialize crypto engine for given node: store keys, create pool |
| * of threads, initialize each one. |
| * |
| * Each thread has unique ID, but 0 and 1 are reserved for receiving and |
| * accepting threads (if export node), so IDs could start from 2, but starting |
| * them from 10 allows easily understand what this thread is for. |
| */ |
| int dst_node_crypto_init(struct dst_node *n, struct dst_crypto_ctl *ctl) |
| { |
| void *key = (ctl + 1); |
| int err = -ENOMEM, i; |
| char name[32]; |
| |
| if (ctl->hash_keysize) { |
| n->hash_key = kmalloc(ctl->hash_keysize, GFP_KERNEL); |
| if (!n->hash_key) |
| goto err_out_exit; |
| memcpy(n->hash_key, key, ctl->hash_keysize); |
| } |
| |
| if (ctl->cipher_keysize) { |
| n->cipher_key = kmalloc(ctl->cipher_keysize, GFP_KERNEL); |
| if (!n->cipher_key) |
| goto err_out_free_hash; |
| memcpy(n->cipher_key, key, ctl->cipher_keysize); |
| } |
| memcpy(&n->crypto, ctl, sizeof(struct dst_crypto_ctl)); |
| |
| for (i = 0; i < ctl->thread_num; ++i) { |
| snprintf(name, sizeof(name), "%s-crypto-%d", n->name, i); |
| /* Unique ids... */ |
| err = thread_pool_add_worker(n->pool, name, i + 10, |
| dst_crypto_thread_init, dst_crypto_thread_cleanup, n); |
| if (err) |
| goto err_out_free_threads; |
| } |
| |
| return 0; |
| |
| err_out_free_threads: |
| while (--i >= 0) |
| thread_pool_del_worker_id(n->pool, i+10); |
| |
| if (ctl->cipher_keysize) |
| kfree(n->cipher_key); |
| ctl->cipher_keysize = 0; |
| err_out_free_hash: |
| if (ctl->hash_keysize) |
| kfree(n->hash_key); |
| ctl->hash_keysize = 0; |
| err_out_exit: |
| return err; |
| } |
| |
| void dst_node_crypto_exit(struct dst_node *n) |
| { |
| struct dst_crypto_ctl *ctl = &n->crypto; |
| |
| if (ctl->cipher_algo[0] || ctl->hash_algo[0]) { |
| kfree(n->hash_key); |
| kfree(n->cipher_key); |
| } |
| } |
| |
| /* |
| * Thrad pool setup callback. Just stores a transaction in private data. |
| */ |
| static int dst_trans_crypto_setup(void *crypto_engine, void *trans) |
| { |
| struct dst_crypto_engine *e = crypto_engine; |
| |
| e->private = trans; |
| return 0; |
| } |
| |
| #if 0 |
| static void dst_dump_bio(struct bio *bio) |
| { |
| u8 *p; |
| struct bio_vec *bv; |
| int i; |
| |
| bio_for_each_segment(bv, bio, i) { |
| dprintk("%s: %llu/%u: size: %u, offset: %u, data: ", |
| __func__, bio->bi_sector, bio->bi_size, |
| bv->bv_len, bv->bv_offset); |
| |
| p = kmap(bv->bv_page) + bv->bv_offset; |
| for (i = 0; i < bv->bv_len; ++i) |
| printk(KERN_DEBUG "%02x ", p[i]); |
| kunmap(bv->bv_page); |
| printk("\n"); |
| } |
| } |
| #endif |
| |
| /* |
| * Encrypt/hash data and send it to the network. |
| */ |
| static int dst_crypto_process_sending(struct dst_crypto_engine *e, |
| struct bio *bio, u8 *hash) |
| { |
| int err; |
| |
| if (e->cipher) { |
| err = dst_crypt(e, bio); |
| if (err) |
| goto err_out_exit; |
| } |
| |
| if (e->hash) { |
| err = dst_hash(e, bio, hash); |
| if (err) |
| goto err_out_exit; |
| |
| #ifdef CONFIG_DST_DEBUG |
| { |
| unsigned int i; |
| |
| /* dst_dump_bio(bio); */ |
| |
| printk(KERN_DEBUG "%s: bio: %llu/%u, rw: %lu, hash: ", |
| __func__, (u64)bio->bi_sector, |
| bio->bi_size, bio_data_dir(bio)); |
| for (i = 0; i < crypto_hash_digestsize(e->hash); ++i) |
| printk("%02x ", hash[i]); |
| printk("\n"); |
| } |
| #endif |
| } |
| |
| return 0; |
| |
| err_out_exit: |
| return err; |
| } |
| |
| /* |
| * Check if received data is valid. Decipher if it is. |
| */ |
| static int dst_crypto_process_receiving(struct dst_crypto_engine *e, |
| struct bio *bio, u8 *hash, u8 *recv_hash) |
| { |
| int err; |
| |
| if (e->hash) { |
| int mismatch; |
| |
| err = dst_hash(e, bio, hash); |
| if (err) |
| goto err_out_exit; |
| |
| mismatch = !!memcmp(recv_hash, hash, |
| crypto_hash_digestsize(e->hash)); |
| #ifdef CONFIG_DST_DEBUG |
| /* dst_dump_bio(bio); */ |
| |
| printk(KERN_DEBUG "%s: bio: %llu/%u, rw: %lu, hash mismatch: %d", |
| __func__, (u64)bio->bi_sector, bio->bi_size, |
| bio_data_dir(bio), mismatch); |
| if (mismatch) { |
| unsigned int i; |
| |
| printk(", recv/calc: "); |
| for (i = 0; i < crypto_hash_digestsize(e->hash); ++i) |
| printk("%02x/%02x ", recv_hash[i], hash[i]); |
| |
| } |
| printk("\n"); |
| #endif |
| err = -1; |
| if (mismatch) |
| goto err_out_exit; |
| } |
| |
| if (e->cipher) { |
| err = dst_crypt(e, bio); |
| if (err) |
| goto err_out_exit; |
| } |
| |
| return 0; |
| |
| err_out_exit: |
| return err; |
| } |
| |
| /* |
| * Thread pool callback to encrypt data and send it to the netowork. |
| */ |
| static int dst_trans_crypto_action(void *crypto_engine, void *schedule_data) |
| { |
| struct dst_crypto_engine *e = crypto_engine; |
| struct dst_trans *t = schedule_data; |
| struct bio *bio = t->bio; |
| int err; |
| |
| dprintk("%s: t: %p, gen: %llu, cipher: %p, hash: %p.\n", |
| __func__, t, t->gen, e->cipher, e->hash); |
| |
| e->enc = t->enc; |
| e->iv = dst_gen_iv(t); |
| |
| if (bio_data_dir(bio) == WRITE) { |
| err = dst_crypto_process_sending(e, bio, t->cmd.hash); |
| if (err) |
| goto err_out_exit; |
| |
| if (e->hash) { |
| t->cmd.csize = crypto_hash_digestsize(e->hash); |
| t->cmd.size += t->cmd.csize; |
| } |
| |
| return dst_trans_send(t); |
| } else { |
| u8 *hash = e->data + e->size/2; |
| |
| err = dst_crypto_process_receiving(e, bio, hash, t->cmd.hash); |
| if (err) |
| goto err_out_exit; |
| |
| dst_trans_remove(t); |
| dst_trans_put(t); |
| } |
| |
| return 0; |
| |
| err_out_exit: |
| t->error = err; |
| dst_trans_put(t); |
| return err; |
| } |
| |
| /* |
| * Schedule crypto processing for given transaction. |
| */ |
| int dst_trans_crypto(struct dst_trans *t) |
| { |
| struct dst_node *n = t->n; |
| int err; |
| |
| err = thread_pool_schedule(n->pool, |
| dst_trans_crypto_setup, dst_trans_crypto_action, |
| t, MAX_SCHEDULE_TIMEOUT); |
| if (err) |
| goto err_out_exit; |
| |
| return 0; |
| |
| err_out_exit: |
| dst_trans_put(t); |
| return err; |
| } |
| |
| /* |
| * Crypto machinery for the export node. |
| */ |
| static int dst_export_crypto_setup(void *crypto_engine, void *bio) |
| { |
| struct dst_crypto_engine *e = crypto_engine; |
| |
| e->private = bio; |
| return 0; |
| } |
| |
| static int dst_export_crypto_action(void *crypto_engine, void *schedule_data) |
| { |
| struct dst_crypto_engine *e = crypto_engine; |
| struct bio *bio = schedule_data; |
| struct dst_export_priv *p = bio->bi_private; |
| int err; |
| |
| dprintk("%s: e: %p, data: %p, bio: %llu/%u, dir: %lu.\n", |
| __func__, e, e->data, (u64)bio->bi_sector, |
| bio->bi_size, bio_data_dir(bio)); |
| |
| e->enc = (bio_data_dir(bio) == READ); |
| e->iv = p->cmd.id; |
| |
| if (bio_data_dir(bio) == WRITE) { |
| u8 *hash = e->data + e->size/2; |
| |
| err = dst_crypto_process_receiving(e, bio, hash, p->cmd.hash); |
| if (err) |
| goto err_out_exit; |
| |
| generic_make_request(bio); |
| } else { |
| err = dst_crypto_process_sending(e, bio, p->cmd.hash); |
| if (err) |
| goto err_out_exit; |
| |
| if (e->hash) { |
| p->cmd.csize = crypto_hash_digestsize(e->hash); |
| p->cmd.size += p->cmd.csize; |
| } |
| |
| err = dst_export_send_bio(bio); |
| } |
| return 0; |
| |
| err_out_exit: |
| bio_put(bio); |
| return err; |
| } |
| |
| int dst_export_crypto(struct dst_node *n, struct bio *bio) |
| { |
| int err; |
| |
| err = thread_pool_schedule(n->pool, |
| dst_export_crypto_setup, dst_export_crypto_action, |
| bio, MAX_SCHEDULE_TIMEOUT); |
| if (err) |
| goto err_out_exit; |
| |
| return 0; |
| |
| err_out_exit: |
| bio_put(bio); |
| return err; |
| } |