diff options
Diffstat (limited to 'ncr-sessions.c')
-rw-r--r-- | ncr-sessions.c | 458 |
1 files changed, 322 insertions, 136 deletions
diff --git a/ncr-sessions.c b/ncr-sessions.c index 84433cc..c65db2f 100644 --- a/ncr-sessions.c +++ b/ncr-sessions.c @@ -31,9 +31,40 @@ #include <linux/scatterlist.h> #include <net/netlink.h> -static int _ncr_session_update_key(struct ncr_lists *lists, ncr_session_t ses, +struct session_item_st { + atomic_t refcnt; + /* Constant values throughout the life of this object follow. */ + ncr_session_t desc; + const struct algo_properties_st *algorithm; + ncr_crypto_op_t op; + struct key_item_st *key; + + /* Variable values, protected usually by mutex, follow. */ + struct mutex mutex; + + /* contexts for various options. + * simpler to have them like that than + * in a union. + */ + struct cipher_data cipher; + struct ncr_pk_ctx pk; + struct hash_data hash; + /* This is a hack, ideally we'd have a hash algorithm that simply + outputs its input as a digest. We'd still need to distinguish + between the hash to identify in the signature and the hash to + actually use, though. */ + void *transparent_hash; + + struct scatterlist *sg; + struct page **pages; + unsigned array_size; + unsigned available_pages; +}; + +static void _ncr_sessions_item_put(struct session_item_st *item); +static int _ncr_session_update_key(struct ncr_lists *lists, + struct session_item_st *sess, struct nlattr *tb[]); -static void _ncr_session_remove(struct ncr_lists *lst, ncr_session_t desc); static int session_list_deinit_fn(int id, void *item, void *unused) { @@ -53,12 +84,57 @@ void ncr_sessions_list_deinit(struct ncr_lists *lst) mutex_unlock(&lst->session_idr_mutex); } +/* Allocate a descriptor without making a sesssion available to userspace. */ +static ncr_session_t session_alloc_desc(struct ncr_lists *lst) +{ + int ret, desc; + + mutex_lock(&lst->session_idr_mutex); + if (idr_pre_get(&lst->session_idr, GFP_KERNEL) == 0) { + ret = -ENOMEM; + goto end; + } + /* idr_pre_get() should preallocate enough, and, due to + session_idr_mutex, nobody else can use the preallocated data. + Therefore the loop recommended in idr_get_new() documentation is not + necessary. */ + ret = idr_get_new(&lst->session_idr, NULL, &desc); + if (ret != 0) + goto end; + ret = desc; +end: + mutex_unlock(&lst->session_idr_mutex); + return ret; +} + +/* Drop a pre-allocated, unpublished session descriptor */ +static void session_drop_desc(struct ncr_lists *lst, ncr_session_t desc) +{ + mutex_lock(&lst->session_idr_mutex); + idr_remove(&lst->session_idr, desc); + mutex_unlock(&lst->session_idr_mutex); +} + +/* Make a session descriptor visible in user-space, stealing the reference */ +static void session_publish_ref(struct ncr_lists *lst, + struct session_item_st *sess) +{ + void *old; + + mutex_lock(&lst->session_idr_mutex); + old = idr_replace(&lst->session_idr, sess, sess->desc); + mutex_unlock(&lst->session_idr_mutex); + BUG_ON(old != NULL); +} + /* returns the data item corresponding to desc */ -struct session_item_st* ncr_sessions_item_get(struct ncr_lists *lst, ncr_session_t desc) +static struct session_item_st *session_get_ref(struct ncr_lists *lst, + ncr_session_t desc) { struct session_item_st* item; mutex_lock(&lst->session_idr_mutex); + /* item may be NULL for pre-allocated session IDs. */ item = idr_find(&lst->session_idr, desc); if (item != NULL) { atomic_inc(&item->refcnt); @@ -71,7 +147,24 @@ struct session_item_st* item; return NULL; } -void _ncr_sessions_item_put( struct session_item_st* item) +/* Find a session, stealing the reference, but keep the descriptor allocated. */ +static struct session_item_st *session_unpublish_ref(struct ncr_lists *lst, + ncr_session_t desc) +{ + struct session_item_st *sess; + + mutex_lock(&lst->session_idr_mutex); + /* sess may be NULL for pre-allocated session IDs. */ + sess = idr_replace(&lst->session_idr, NULL, desc); + mutex_unlock(&lst->session_idr_mutex); + if (sess != NULL && !IS_ERR(sess)) + return sess; + + err(); + return NULL; +} + +static void _ncr_sessions_item_put(struct session_item_st *item) { if (atomic_dec_and_test(&item->refcnt)) { cryptodev_cipher_deinit(&item->cipher); @@ -86,7 +179,7 @@ void _ncr_sessions_item_put( struct session_item_st* item) } } -struct session_item_st* ncr_session_new(struct ncr_lists *lst) +static struct session_item_st *ncr_session_new(ncr_session_t desc) { struct session_item_st* sess; @@ -105,21 +198,10 @@ struct session_item_st* ncr_session_new(struct ncr_lists *lst) err(); goto err_sess; } - mutex_init(&sess->mem_mutex); + mutex_init(&sess->mutex); - atomic_set(&sess->refcnt, 2); /* One for lst->list, one for "sess" */ - - mutex_lock(&lst->session_idr_mutex); - /* idr_pre_get() should preallocate enough, and, due to - session_idr_mutex, nobody else can use the preallocated data. - Therefore the loop recommended in idr_get_new() documentation is not - necessary. */ - if (idr_pre_get(&lst->session_idr, GFP_KERNEL) == 0 || - idr_get_new(&lst->session_idr, sess, &sess->desc) != 0) { - mutex_unlock(&lst->session_idr_mutex); - goto err_sess; - } - mutex_unlock(&lst->session_idr_mutex); + atomic_set(&sess->refcnt, 1); + sess->desc = desc; return sess; @@ -264,27 +346,75 @@ static int key_item_get_nla_read(struct key_item_st **st, return ret; } -static int _ncr_session_init(struct ncr_lists *lists, ncr_crypto_op_t op, - struct nlattr *tb[]) +/* The caller is responsible for locking of "session". old_session must not be + locked on entry. */ +static int +init_or_clone_hash(struct session_item_st *session, + struct session_item_st *old_session, + const struct algo_properties_st *algo, + const void *mac_key, size_t mac_key_size) +{ + int ret; + + if (old_session == NULL) + return cryptodev_hash_init(&session->hash, algo->kstr, + mac_key, mac_key_size); + + if (mutex_lock_interruptible(&old_session->mutex)) { + err(); + return -ERESTARTSYS; + } + ret = cryptodev_hash_clone(&session->hash, &old_session->hash, mac_key, + mac_key_size); + mutex_unlock(&old_session->mutex); + + return ret; +} + +static struct session_item_st *_ncr_session_init(struct ncr_lists *lists, + ncr_session_t desc, + ncr_crypto_op_t op, + struct nlattr *tb[]) { const struct nlattr *nla; - struct session_item_st* ns = NULL; + struct session_item_st *ns, *old_session = NULL; int ret; const struct algo_properties_st *sign_hash; - ns = ncr_session_new(lists); + ns = ncr_session_new(desc); if (ns == NULL) { err(); - return -ENOMEM; + return ERR_PTR(-ENOMEM); } + /* ns is the only reference throughout this function, so no locking + is necessary. */ ns->op = op; - ns->algorithm = _ncr_nla_to_properties(tb[NCR_ATTR_ALGORITHM]); - if (ns->algorithm == NULL) { - err(); - ret = -EINVAL; - goto fail; + nla = tb[NCR_ATTR_SESSION_CLONE_FROM]; + if (nla != NULL) { + /* "ns" is not visible to userspace, so this is safe. */ + old_session = session_get_ref(lists, nla_get_u32(nla)); + if (old_session == NULL) { + err(); + ret = -EINVAL; + goto fail; + } + if (ns->op != old_session->op) { + err(); + ret = -EINVAL; + goto fail; + } } + + if (old_session == NULL) { + ns->algorithm = _ncr_nla_to_properties(tb[NCR_ATTR_ALGORITHM]); + if (ns->algorithm == NULL) { + err(); + ret = -EINVAL; + goto fail; + } + } else + ns->algorithm = old_session->algorithm; switch(op) { case NCR_OP_ENCRYPT: @@ -295,6 +425,12 @@ static int _ncr_session_init(struct ncr_lists *lists, ncr_crypto_op_t op, goto fail; } + if (old_session != NULL) { + err(); + ret = -EOPNOTSUPP; + goto fail; + } + /* read key */ ret = key_item_get_nla_read(&ns->key, lists, tb[NCR_ATTR_KEY]); @@ -305,7 +441,7 @@ static int _ncr_session_init(struct ncr_lists *lists, ncr_crypto_op_t op, /* wrapping keys cannot be used for encryption or decryption */ - if (ns->key->flags & NCR_KEY_FLAG_WRAPPING) { + if (ns->key->flags & NCR_KEY_FLAG_WRAPPING || ns->key->flags & NCR_KEY_FLAG_UNWRAPPING) { err(); ret = -EINVAL; goto fail; @@ -370,24 +506,32 @@ static int _ncr_session_init(struct ncr_lists *lists, ncr_crypto_op_t op, goto fail; } - ret = cryptodev_hash_init(&ns->hash, ns->algorithm->kstr, 0, NULL, 0); + ret = init_or_clone_hash(ns, old_session, + ns->algorithm, NULL, + 0); if (ret < 0) { err(); goto fail; } } else { - /* read key */ - ret = key_item_get_nla_read(&ns->key, lists, - tb[NCR_ATTR_KEY]); - if (ret < 0) { - err(); - goto fail; + /* Get key */ + if (old_session == NULL) { + ret = key_item_get_nla_read(&ns->key, + lists, + tb[NCR_ATTR_KEY]); + if (ret < 0) { + err(); + goto fail; + } + } else { + atomic_inc(&old_session->key->refcnt); + ns->key = old_session->key; } /* wrapping keys cannot be used for anything except wrapping. */ - if (ns->key->flags & NCR_KEY_FLAG_WRAPPING) { + if (ns->key->flags & NCR_KEY_FLAG_WRAPPING || ns->key->flags & NCR_KEY_FLAG_UNWRAPPING) { err(); ret = -EINVAL; goto fail; @@ -400,7 +544,7 @@ static int _ncr_session_init(struct ncr_lists *lists, ncr_crypto_op_t op, goto fail; } - ret = cryptodev_hash_init(&ns->hash, ns->algorithm->kstr, 1, + ret = init_or_clone_hash(ns, old_session, ns->algorithm, ns->key->key.secret.data, ns->key->key.secret.size); if (ret < 0) { err(); @@ -408,6 +552,12 @@ static int _ncr_session_init(struct ncr_lists *lists, ncr_crypto_op_t op, } } else if (ns->algorithm->is_pk && (ns->key->type == NCR_KEY_TYPE_PRIVATE || ns->key->type == NCR_KEY_TYPE_PUBLIC)) { + if (old_session != NULL) { + err(); + ret = -EOPNOTSUPP; + goto fail; + } + nla = tb[NCR_ATTR_SIGNATURE_HASH_ALGORITHM]; sign_hash = _ncr_nla_to_properties(nla); if (sign_hash == NULL) { @@ -435,7 +585,7 @@ static int _ncr_session_init(struct ncr_lists *lists, ncr_crypto_op_t op, goto fail; } - ret = cryptodev_hash_init(&ns->hash, sign_hash->kstr, 0, NULL, 0); + ret = cryptodev_hash_init(&ns->hash, sign_hash->kstr, NULL, 0); if (ret < 0) { err(); goto fail; @@ -462,25 +612,46 @@ static int _ncr_session_init(struct ncr_lists *lists, ncr_crypto_op_t op, ret = -EINVAL; goto fail; } + + if (old_session != NULL) + _ncr_sessions_item_put(old_session); - ret = ns->desc; + return ns; fail: - if (ret < 0) { - _ncr_session_remove(lists, ns->desc); - } + if (old_session != NULL) + _ncr_sessions_item_put(old_session); _ncr_sessions_item_put(ns); - return ret; + return ERR_PTR(ret); } int ncr_session_init(struct ncr_lists *lists, const struct ncr_session_init *session, struct nlattr *tb[]) { - return _ncr_session_init(lists, session->op, tb); + ncr_session_t desc; + struct session_item_st *sess; + + desc = session_alloc_desc(lists); + if (desc < 0) { + err(); + return desc; + } + + sess = _ncr_session_init(lists, desc, session->op, tb); + if (IS_ERR(sess)) { + err(); + session_drop_desc(lists, desc); + return PTR_ERR(sess); + } + + session_publish_ref(lists, sess); + + return desc; } +/* The caller is responsible for locking of the session. */ static int _ncr_session_encrypt(struct session_item_st* sess, const struct scatterlist* input, unsigned input_cnt, size_t input_size, void *output, unsigned output_cnt, size_t *output_size) { @@ -509,6 +680,7 @@ int ret; return 0; } +/* The caller is responsible for locking of the session. */ static int _ncr_session_decrypt(struct session_item_st* sess, const struct scatterlist* input, unsigned input_cnt, size_t input_size, struct scatterlist *output, unsigned output_cnt, size_t *output_size) @@ -538,20 +710,7 @@ int ret; return 0; } -static void _ncr_session_remove(struct ncr_lists *lst, ncr_session_t desc) -{ - struct session_item_st * item; - - mutex_lock(&lst->session_idr_mutex); - item = idr_find(&lst->session_idr, desc); - if (item != NULL) - idr_remove(&lst->session_idr, desc); /* Steal the reference */ - mutex_unlock(&lst->session_idr_mutex); - - if (item != NULL) - _ncr_sessions_item_put(item); -} - +/* The caller is responsible for locking of the session. */ static int _ncr_session_grow_pages(struct session_item_st *ses, int pagecount) { struct scatterlist *sg; @@ -583,7 +742,8 @@ static int _ncr_session_grow_pages(struct session_item_st *ses, int pagecount) } /* Make NCR_ATTR_UPDATE_INPUT_DATA and NCR_ATTR_UPDATE_OUTPUT_BUFFER available - in scatterlists */ + in scatterlists. + The caller is responsible for locking of the session. */ static int get_userbuf2(struct session_item_st *ses, struct nlattr *tb[], struct scatterlist **src_sg, unsigned *src_cnt, size_t *src_size, struct ncr_session_output_buffer *dst, @@ -673,35 +833,24 @@ static int get_userbuf2(struct session_item_st *ses, struct nlattr *tb[], return 0; } -/* Called when userspace buffers are used */ -static int _ncr_session_update(struct ncr_lists *lists, ncr_session_t ses, +/* Called when userspace buffers are used. + The caller is responsible for locking of the session. */ +static int _ncr_session_update(struct session_item_st *sess, struct nlattr *tb[], int compat) { + const struct nlattr *nla; int ret; - struct session_item_st* sess; struct scatterlist *isg = NULL; struct scatterlist *osg = NULL; unsigned osg_cnt=0, isg_cnt=0; size_t isg_size = 0, osg_size; struct ncr_session_output_buffer out; - sess = ncr_sessions_item_get(lists, ses); - if (sess == NULL) { - err(); - return -EINVAL; - } - - if (mutex_lock_interruptible(&sess->mem_mutex)) { - err(); - _ncr_sessions_item_put(sess); - return -ERESTARTSYS; - } - ret = get_userbuf2(sess, tb, &isg, &isg_cnt, &isg_size, &out, &osg, &osg_cnt, compat); if (ret < 0) { err(); - goto fail; + return ret; } switch(sess->op) { @@ -719,6 +868,15 @@ static int _ncr_session_update(struct ncr_lists *lists, ncr_session_t ses, goto fail; } + if (sess->algorithm->is_symmetric + && sess->algorithm->needs_iv) { + nla = tb[NCR_ATTR_IV]; + if (nla != NULL) + cryptodev_cipher_set_iv(&sess->cipher, + nla_data(nla), + nla_len(nla)); + } + ret = _ncr_session_encrypt(sess, isg, isg_cnt, isg_size, osg, osg_cnt, &osg_size); if (ret < 0) { @@ -747,6 +905,15 @@ static int _ncr_session_update(struct ncr_lists *lists, ncr_session_t ses, goto fail; } + if (sess->algorithm->is_symmetric + && sess->algorithm->needs_iv) { + nla = tb[NCR_ATTR_IV]; + if (nla != NULL) + cryptodev_cipher_set_iv(&sess->cipher, + nla_data(nla), + nla_len(nla)); + } + ret = _ncr_session_decrypt(sess, isg, isg_cnt, isg_size, osg, osg_cnt, &osg_size); if (ret < 0) { @@ -800,52 +967,42 @@ fail: release_user_pages(sess->pages, sess->available_pages); sess->available_pages = 0; } - mutex_unlock(&sess->mem_mutex); - _ncr_sessions_item_put(sess); return ret; } -static int try_session_update(struct ncr_lists *lists, ncr_session_t ses, - struct nlattr *tb[], int compat) +/* The caller is responsible for locking of the session. */ +static int try_session_update(struct ncr_lists *lists, + struct session_item_st *sess, struct nlattr *tb[], + int compat) { if (tb[NCR_ATTR_UPDATE_INPUT_KEY_AS_DATA] != NULL) - return _ncr_session_update_key(lists, ses, tb); + return _ncr_session_update_key(lists, sess, tb); else if (tb[NCR_ATTR_UPDATE_INPUT_DATA] != NULL) - return _ncr_session_update(lists, ses, tb, compat); - - return 0; + return _ncr_session_update(sess, tb, compat); + else + return 0; } -static int _ncr_session_final(struct ncr_lists *lists, ncr_session_t ses, - struct nlattr *tb[], int compat) +/* The caller is responsible for locking of the session. + Note that one or more _ncr_session_update()s may still be blocked on + sess->mutex and will execute after this function! */ +static int _ncr_session_final(struct ncr_lists *lists, + struct session_item_st *sess, struct nlattr *tb[], + int compat) { const struct nlattr *nla; int ret; - struct session_item_st* sess; int digest_size; uint8_t digest[NCR_HASH_MAX_OUTPUT_SIZE]; void *buffer = NULL; - sess = ncr_sessions_item_get(lists, ses); - if (sess == NULL) { - err(); - return -EINVAL; - } - - ret = try_session_update(lists, ses, tb, compat); + ret = try_session_update(lists, sess, tb, compat); if (ret < 0) { err(); - _ncr_sessions_item_put(sess); return ret; } - if (mutex_lock_interruptible(&sess->mem_mutex)) { - err(); - _ncr_sessions_item_put(sess); - return -ERESTARTSYS; - } - switch(sess->op) { case NCR_OP_ENCRYPT: case NCR_OP_DECRYPT: @@ -983,42 +1140,26 @@ static int _ncr_session_final(struct ncr_lists *lists, ncr_session_t ses, } fail: - mutex_unlock(&sess->mem_mutex); kfree(buffer); - cryptodev_hash_deinit(&sess->hash); - if (sess->algorithm->is_symmetric) { - cryptodev_cipher_deinit(&sess->cipher); - } else { - ncr_pk_cipher_deinit(&sess->pk); - } - - _ncr_sessions_item_put(sess); - _ncr_session_remove(lists, ses); - return ret; } -/* Direct with key: Allows to hash a key */ -static int _ncr_session_update_key(struct ncr_lists *lists, ncr_session_t ses, +/* Direct with key: Allows to hash a key. + The caller is responsible for locking of the session. */ +static int _ncr_session_update_key(struct ncr_lists *lists, + struct session_item_st* sess, struct nlattr *tb[]) { int ret; - struct session_item_st* sess; struct key_item_st* key = NULL; - sess = ncr_sessions_item_get(lists, ses); - if (sess == NULL) { - err(); - return -EINVAL; - } - /* read key */ ret = key_item_get_nla_read(&key, lists, tb[NCR_ATTR_UPDATE_INPUT_KEY_AS_DATA]); if (ret < 0) { err(); - goto fail; + return ret; } if (key->type != NCR_KEY_TYPE_SECRET) { @@ -1056,8 +1197,7 @@ static int _ncr_session_update_key(struct ncr_lists *lists, ncr_session_t ses, ret = 0; fail: - if (key) _ncr_key_item_put(key); - _ncr_sessions_item_put(sess); + _ncr_key_item_put(key); return ret; } @@ -1066,14 +1206,33 @@ int ncr_session_update(struct ncr_lists *lists, const struct ncr_session_update *op, struct nlattr *tb[], int compat) { + struct session_item_st *sess; int ret; + sess = session_get_ref(lists, op->ses); + if (sess == NULL) { + err(); + return -EINVAL; + } + + /* Note that op->ses may be reallocated from now on, making the audit + information confusing. */ + + if (mutex_lock_interruptible(&sess->mutex)) { + err(); + ret = -ERESTARTSYS; + goto end; + } if (tb[NCR_ATTR_UPDATE_INPUT_DATA] != NULL) - ret = _ncr_session_update(lists, op->ses, tb, compat); + ret = _ncr_session_update(sess, tb, compat); else if (tb[NCR_ATTR_UPDATE_INPUT_KEY_AS_DATA] != NULL) - ret = _ncr_session_update_key(lists, op->ses, tb); + ret = _ncr_session_update_key(lists, sess, tb); else ret = -EINVAL; + mutex_unlock(&sess->mutex); + +end: + _ncr_sessions_item_put(sess); if (unlikely(ret)) { err(); @@ -1087,26 +1246,53 @@ int ncr_session_final(struct ncr_lists *lists, const struct ncr_session_final *op, struct nlattr *tb[], int compat) { - return _ncr_session_final(lists, op->ses, tb, compat); + struct session_item_st *sess; + int ret; + + /* Make the session inaccessible atomically to avoid concurrent + session_final() callers, but keep the ID allocated to keep audit + information unambiguous. */ + sess = session_unpublish_ref(lists, op->ses); + if (sess == NULL) { + err(); + return -EINVAL; + } + + if (mutex_lock_interruptible(&sess->mutex)) { + err(); + /* Other threads may observe the session descriptor + disappearing and reappearing - but then they should not be + accessing it anyway if it is being freed. + session_unpublish_ref keeps the ID allocated for us. */ + session_publish_ref(lists, sess); + return -ERESTARTSYS; + } + ret = _ncr_session_final(lists, sess, tb, compat); + mutex_unlock(&sess->mutex); + + _ncr_sessions_item_put(sess); + session_drop_desc(lists, op->ses); + + return ret; } int ncr_session_once(struct ncr_lists *lists, const struct ncr_session_once *once, struct nlattr *tb[], int compat) { + struct session_item_st *sess; int ret; - ret = _ncr_session_init(lists, once->op, tb); - if (ret < 0) { + sess = _ncr_session_init(lists, -1, once->op, tb); + if (IS_ERR(sess)) { err(); - return ret; + return PTR_ERR(sess); } - ret = _ncr_session_final(lists, ret, tb, compat); - if (ret < 0) { - err(); - return ret; - } + /* No locking of sess necessary, "sess" is the only reference. */ + ret = _ncr_session_final(lists, sess, tb, compat); + + _ncr_sessions_item_put(sess); return ret; } |