437 lines · 10.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * algif_skcipher: User-space interface for skcipher algorithms * * This file provides the user-space API for symmetric key ciphers. * * Copyright (c) 2010 Herbert Xu <herbert@gondor.apana.org.au> * * The following concept of the memory management is used: * * The kernel maintains two SGLs, the TX SGL and the RX SGL. The TX SGL is * filled by user space with the data submitted via sendmsg. Filling up the TX * SGL does not cause a crypto operation -- the data will only be tracked by * the kernel. Upon receipt of one recvmsg call, the caller must provide a * buffer which is tracked with the RX SGL. * * During the processing of the recvmsg operation, the cipher request is * allocated and prepared. As part of the recvmsg operation, the processed * TX buffers are extracted from the TX SGL into a separate SGL. * * After the completion of the crypto operation, the RX SGL and the cipher * request is released. The extracted TX SGL parts are released together with * the RX SGL release. */ #include <crypto/scatterwalk.h>#include <crypto/skcipher.h>#include <crypto/if_alg.h>#include <linux/init.h>#include <linux/list.h>#include <linux/kernel.h>#include <linux/mm.h>#include <linux/module.h>#include <linux/net.h>#include <linux/string.h>#include <net/sock.h> static const struct af_alg_allowlist_entry skcipher_allowlist[] = {	{ "adiantum(xchacha12,aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */	{ "adiantum(xchacha20,aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */	{ "cbc(aes)" }, /* iwd */	{ "cbc(des)" }, /* iwd */	{ "cbc(des3_ede)" }, /* iwd */	{ "cbc(paes)" }, /* caam and others */	{ "ctr(aes)" }, /* iwd */	{ "ecb(aes)" }, /* iwd, bluez */	{ "ecb(des)" }, /* iwd */	{ "hctr2(aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */	{ "xts(aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup benchmark */	{ "xts(camellia)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */	{ "xts(serpent)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */	{ "xts(twofish)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */	{},}; static int skcipher_sendmsg(struct socket *sock, struct msghdr *msg,			    size_t size){	struct sock *sk = sock->sk;	struct alg_sock *ask = alg_sk(sk);	struct sock *psk = ask->parent;	struct alg_sock *pask = alg_sk(psk);	struct crypto_skcipher *tfm = pask->private;	unsigned ivsize = crypto_skcipher_ivsize(tfm); 	return af_alg_sendmsg(sock, msg, size, ivsize);} static int algif_skcipher_export(struct sock *sk, struct skcipher_request *req){	struct alg_sock *ask = alg_sk(sk);	struct crypto_skcipher *tfm;	struct af_alg_ctx *ctx;	struct alg_sock *pask;	unsigned statesize;	struct sock *psk;	int err; 	if (!(req->base.flags & CRYPTO_SKCIPHER_REQ_NOTFINAL))		return 0; 	ctx = ask->private;	psk = ask->parent;	pask = alg_sk(psk);	tfm = pask->private; 	statesize = crypto_skcipher_statesize(tfm);	ctx->state = sock_kmalloc(sk, statesize, GFP_ATOMIC);	if (!ctx->state)		return -ENOMEM; 	err = crypto_skcipher_export(req, ctx->state);	if (err) {		sock_kzfree_s(sk, ctx->state, statesize);		ctx->state = NULL;	} 	return err;} static int _skcipher_recvmsg(struct socket *sock, struct msghdr *msg,			     size_t ignored, int flags){	struct sock *sk = sock->sk;	struct alg_sock *ask = alg_sk(sk);	struct sock *psk = ask->parent;	struct alg_sock *pask = alg_sk(psk);	struct af_alg_ctx *ctx = ask->private;	struct crypto_skcipher *tfm = pask->private;	unsigned int bs = crypto_skcipher_chunksize(tfm);	struct af_alg_async_req *areq;	unsigned cflags = 0;	int err = 0;	size_t len = 0; 	if (!ctx->init || (ctx->more && ctx->used < bs)) {		err = af_alg_wait_for_data(sk, flags, bs);		if (err)			return err;	} 	/* Allocate cipher request for current operation. */	areq = af_alg_alloc_areq(sk, sizeof(struct af_alg_async_req) +				     crypto_skcipher_reqsize(tfm));	if (IS_ERR(areq))		return PTR_ERR(areq); 	/* convert iovecs of output buffers into RX SGL */	err = af_alg_get_rsgl(sk, msg, flags, areq, ctx->used, &len);	if (err)		goto free; 	/*	 * If more buffers are to be expected to be processed, process only	 * full block size buffers.	 */	if (ctx->more || len < ctx->used) {		if (len < bs) {			err = -EINVAL;			goto free;		} 		len -= len % bs;		cflags |= CRYPTO_SKCIPHER_REQ_NOTFINAL;	} 	/*	 * Create a per request TX SGL for this request which tracks the	 * SG entries from the global TX SGL.	 */	areq->tsgl_entries = af_alg_count_tsgl(sk, len);	if (!areq->tsgl_entries)		areq->tsgl_entries = 1;	areq->tsgl = sock_kmalloc(sk, array_size(sizeof(*areq->tsgl),						 areq->tsgl_entries),				  GFP_KERNEL);	if (!areq->tsgl) {		err = -ENOMEM;		goto free;	}	sg_init_table(areq->tsgl, areq->tsgl_entries);	af_alg_pull_tsgl(sk, len, areq->tsgl); 	/* Initialize the crypto operation */	skcipher_request_set_tfm(&areq->cra_u.skcipher_req, tfm);	skcipher_request_set_crypt(&areq->cra_u.skcipher_req, areq->tsgl,				   areq->first_rsgl.sgl.sgt.sgl, len, ctx->iv); 	if (ctx->state) {		err = crypto_skcipher_import(&areq->cra_u.skcipher_req,					     ctx->state);		sock_kzfree_s(sk, ctx->state, crypto_skcipher_statesize(tfm));		ctx->state = NULL;		if (err)			goto free;		cflags |= CRYPTO_SKCIPHER_REQ_CONT;	} 	skcipher_request_set_callback(&areq->cra_u.skcipher_req,				      cflags |				      CRYPTO_TFM_REQ_MAY_SLEEP |				      CRYPTO_TFM_REQ_MAY_BACKLOG,				      crypto_req_done, &ctx->wait);	err = crypto_wait_req(ctx->enc ?		crypto_skcipher_encrypt(&areq->cra_u.skcipher_req) :		crypto_skcipher_decrypt(&areq->cra_u.skcipher_req),					 &ctx->wait); 	if (!err)		err = algif_skcipher_export(			sk, &areq->cra_u.skcipher_req); free:	af_alg_free_resources(areq); 	return err ? err : len;} static int skcipher_recvmsg(struct socket *sock, struct msghdr *msg,			    size_t ignored, int flags){	struct sock *sk = sock->sk;	int ret = 0; 	lock_sock(sk);	while (msg_data_left(msg)) {		int err = _skcipher_recvmsg(sock, msg, ignored, flags); 		/*		 * This error covers -EIOCBQUEUED which implies that we can		 * only handle one AIO request. If the caller wants to have		 * multiple AIO requests in parallel, he must make multiple		 * separate AIO calls.		 *		 * Also return the error if no data has been processed so far.		 */		if (err <= 0) {			if (err == -EIOCBQUEUED || !ret)				ret = err;			goto out;		} 		ret += err;	} out:	af_alg_wmem_wakeup(sk);	release_sock(sk);	return ret;} static struct proto_ops algif_skcipher_ops = {	.family		=	PF_ALG, 	.connect	=	sock_no_connect,	.socketpair	=	sock_no_socketpair,	.getname	=	sock_no_getname,	.ioctl		=	sock_no_ioctl,	.listen		=	sock_no_listen,	.shutdown	=	sock_no_shutdown,	.mmap		=	sock_no_mmap,	.bind		=	sock_no_bind,	.accept		=	sock_no_accept, 	.release	=	af_alg_release,	.sendmsg	=	skcipher_sendmsg,	.recvmsg	=	skcipher_recvmsg,	.poll		=	af_alg_poll,}; static int skcipher_check_key(struct socket *sock){	int err = 0;	struct sock *psk;	struct alg_sock *pask;	struct crypto_skcipher *tfm;	struct sock *sk = sock->sk;	struct alg_sock *ask = alg_sk(sk); 	lock_sock(sk);	if (!atomic_read(&ask->nokey_refcnt))		goto unlock_child; 	psk = ask->parent;	pask = alg_sk(ask->parent);	tfm = pask->private; 	err = -ENOKEY;	lock_sock_nested(psk, SINGLE_DEPTH_NESTING);	if (crypto_skcipher_get_flags(tfm) & CRYPTO_TFM_NEED_KEY)		goto unlock; 	atomic_dec(&pask->nokey_refcnt);	atomic_set(&ask->nokey_refcnt, 0); 	err = 0; unlock:	release_sock(psk);unlock_child:	release_sock(sk); 	return err;} static int skcipher_sendmsg_nokey(struct socket *sock, struct msghdr *msg,				  size_t size){	int err; 	err = skcipher_check_key(sock);	if (err)		return err; 	return skcipher_sendmsg(sock, msg, size);} static int skcipher_recvmsg_nokey(struct socket *sock, struct msghdr *msg,				  size_t ignored, int flags){	int err; 	err = skcipher_check_key(sock);	if (err)		return err; 	return skcipher_recvmsg(sock, msg, ignored, flags);} static struct proto_ops algif_skcipher_ops_nokey = {	.family		=	PF_ALG, 	.connect	=	sock_no_connect,	.socketpair	=	sock_no_socketpair,	.getname	=	sock_no_getname,	.ioctl		=	sock_no_ioctl,	.listen		=	sock_no_listen,	.shutdown	=	sock_no_shutdown,	.mmap		=	sock_no_mmap,	.bind		=	sock_no_bind,	.accept		=	sock_no_accept, 	.release	=	af_alg_release,	.sendmsg	=	skcipher_sendmsg_nokey,	.recvmsg	=	skcipher_recvmsg_nokey,	.poll		=	af_alg_poll,}; static void *skcipher_bind(const char *name){	u32 mask = AF_ALG_CRYPTOAPI_MASK;	int err; 	err = af_alg_check_restriction(name, skcipher_allowlist);	if (err)		return ERR_PTR(err); 	if (strcmp(name, "cbc(paes)") == 0)		mask = 0; 	return crypto_alloc_skcipher(name, 0, mask);} static void skcipher_release(void *private){	crypto_free_skcipher(private);} static int skcipher_setkey(void *private, const u8 *key, unsigned int keylen){	return crypto_skcipher_setkey(private, key, keylen);} static void skcipher_sock_destruct(struct sock *sk){	struct alg_sock *ask = alg_sk(sk);	struct af_alg_ctx *ctx = ask->private;	struct sock *psk = ask->parent;	struct alg_sock *pask = alg_sk(psk);	struct crypto_skcipher *tfm = pask->private; 	af_alg_pull_tsgl(sk, ctx->used, NULL);	sock_kzfree_s(sk, ctx->iv, crypto_skcipher_ivsize(tfm));	if (ctx->state)		sock_kzfree_s(sk, ctx->state, crypto_skcipher_statesize(tfm));	sock_kfree_s(sk, ctx, ctx->len);	af_alg_release_parent(sk);} static int skcipher_accept_parent_nokey(void *private, struct sock *sk){	struct af_alg_ctx *ctx;	struct alg_sock *ask = alg_sk(sk);	struct crypto_skcipher *tfm = private;	unsigned int len = sizeof(*ctx); 	ctx = sock_kmalloc(sk, len, GFP_KERNEL);	if (!ctx)		return -ENOMEM;	memset(ctx, 0, len); 	ctx->iv = sock_kmalloc(sk, crypto_skcipher_ivsize(tfm),			       GFP_KERNEL);	if (!ctx->iv) {		sock_kfree_s(sk, ctx, len);		return -ENOMEM;	}	memset(ctx->iv, 0, crypto_skcipher_ivsize(tfm)); 	INIT_LIST_HEAD(&ctx->tsgl_list);	ctx->len = len;	crypto_init_wait(&ctx->wait); 	ask->private = ctx; 	sk->sk_destruct = skcipher_sock_destruct; 	return 0;} static int skcipher_accept_parent(void *private, struct sock *sk){	struct crypto_skcipher *tfm = private; 	if (crypto_skcipher_get_flags(tfm) & CRYPTO_TFM_NEED_KEY)		return -ENOKEY; 	return skcipher_accept_parent_nokey(private, sk);} static const struct af_alg_type algif_type_skcipher = {	.bind		=	skcipher_bind,	.release	=	skcipher_release,	.setkey		=	skcipher_setkey,	.accept		=	skcipher_accept_parent,	.accept_nokey	=	skcipher_accept_parent_nokey,	.ops		=	&algif_skcipher_ops,	.ops_nokey	=	&algif_skcipher_ops_nokey,	.name		=	"skcipher",	.owner		=	THIS_MODULE}; static int __init algif_skcipher_init(void){	return af_alg_register_type(&algif_type_skcipher);} static void __exit algif_skcipher_exit(void){	int err = af_alg_unregister_type(&algif_type_skcipher);	BUG_ON(err);} module_init(algif_skcipher_init);module_exit(algif_skcipher_exit);MODULE_DESCRIPTION("Userspace interface for skcipher algorithms");MODULE_LICENSE("GPL");