/* * 2007+ Copyright (c) Evgeniy Polyakov * 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 #include #include #include #include #include /* * 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; ipage_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; ipages[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; ithread_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; ibv_len; ++i) printk("%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; ihash); ++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; ihash); ++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; }