1316 lines · 28.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * af_alg: User-space algorithm interface * * This file provides the user-space API for algorithms. * * Copyright (c) 2010 Herbert Xu <herbert@gondor.apana.org.au> */ #include <linux/atomic.h>#include <linux/capability.h>#include <crypto/if_alg.h>#include <linux/crypto.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/key.h>#include <linux/key-type.h>#include <linux/list.h>#include <linux/module.h>#include <linux/net.h>#include <linux/rwsem.h>#include <linux/sched.h>#include <linux/sched/signal.h>#include <linux/security.h>#include <linux/string.h>#include <linux/sysctl.h>#include <linux/user_namespace.h>#include <keys/user-type.h>#include <keys/trusted-type.h>#include <keys/encrypted-type.h> static int af_alg_restrict = 1; static const struct ctl_table af_alg_table[] = {	{		.procname       = "af_alg_restrict",		.data           = &af_alg_restrict,		.maxlen         = sizeof(int),		.mode           = 0644,		.proc_handler   = proc_dointvec_minmax,		.extra1		= SYSCTL_ZERO,		.extra2		= SYSCTL_TWO,	},}; static struct ctl_table_header *af_alg_header; struct alg_type_list {	const struct af_alg_type *type;	struct list_head list;}; static struct proto alg_proto = {	.name			= "ALG",	.owner			= THIS_MODULE,	.obj_size		= sizeof(struct alg_sock),}; static LIST_HEAD(alg_types);static DECLARE_RWSEM(alg_types_sem); static const struct af_alg_type *alg_get_type(const char *name){	const struct af_alg_type *type = ERR_PTR(-ENOENT);	struct alg_type_list *node; 	down_read(&alg_types_sem);	list_for_each_entry(node, &alg_types, list) {		if (strcmp(node->type->name, name))			continue; 		if (try_module_get(node->type->owner))			type = node->type;		break;	}	up_read(&alg_types_sem); 	return type;} int af_alg_register_type(const struct af_alg_type *type){	struct alg_type_list *node;	int err = -EEXIST; 	down_write(&alg_types_sem);	list_for_each_entry(node, &alg_types, list) {		if (!strcmp(node->type->name, type->name))			goto unlock;	} 	node = kmalloc_obj(*node);	err = -ENOMEM;	if (!node)		goto unlock; 	type->ops->owner = THIS_MODULE;	if (type->ops_nokey)		type->ops_nokey->owner = THIS_MODULE;	node->type = type;	list_add(&node->list, &alg_types);	err = 0; unlock:	up_write(&alg_types_sem); 	return err;}EXPORT_SYMBOL_GPL(af_alg_register_type); int af_alg_unregister_type(const struct af_alg_type *type){	struct alg_type_list *node;	int err = -ENOENT; 	down_write(&alg_types_sem);	list_for_each_entry(node, &alg_types, list) {		if (strcmp(node->type->name, type->name))			continue; 		list_del(&node->list);		kfree(node);		err = 0;		break;	}	up_write(&alg_types_sem); 	return err;}EXPORT_SYMBOL_GPL(af_alg_unregister_type); static bool af_alg_capable(void){	return ns_capable_noaudit(&init_user_ns, CAP_NET_ADMIN) ||	       capable(CAP_SYS_ADMIN);} int af_alg_check_restriction(const char *name,			     const struct af_alg_allowlist_entry allowlist[]){	int level = READ_ONCE(af_alg_restrict); 	if (level == 0)		return 0;	if (level == 1) {		for (const struct af_alg_allowlist_entry *ent = allowlist;		     ent->name; ent++) {			if (strcmp(name, ent->name) == 0) {				if ((ent->flags & AF_ALG_UNPRIVILEGED) ||				    af_alg_capable())					return 0;				/* List contains at most one entry per name. */				break;			}		}	}	/*	 * Use -ENOENT (the error code for "algorithm not found") instead of	 * -EACCES or -EPERM, for the highest chance of correctly triggering	 * fallback code paths in userspace programs.	 *	 * Don't log a warning, since it would be noisy.  iwd tries to bind a	 * bunch of algorithms that it never uses.	 */	return -ENOENT;}EXPORT_SYMBOL_GPL(af_alg_check_restriction); static void alg_do_release(const struct af_alg_type *type, void *private){	if (!type)		return; 	type->release(private);	module_put(type->owner);} int af_alg_release(struct socket *sock){	if (sock->sk) {		sock_put(sock->sk);		sock->sk = NULL;	}	return 0;}EXPORT_SYMBOL_GPL(af_alg_release); void af_alg_release_parent(struct sock *sk){	struct alg_sock *ask = alg_sk(sk);	unsigned int nokey = atomic_read(&ask->nokey_refcnt); 	sk = ask->parent;	ask = alg_sk(sk); 	if (nokey)		atomic_dec(&ask->nokey_refcnt); 	if (atomic_dec_and_test(&ask->refcnt))		sock_put(sk);}EXPORT_SYMBOL_GPL(af_alg_release_parent); static int alg_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int addr_len){	const u32 allowed = CRYPTO_ALG_KERN_DRIVER_ONLY;	struct sock *sk = sock->sk;	struct alg_sock *ask = alg_sk(sk);	struct sockaddr_alg_new *sa = (void *)uaddr;	const struct af_alg_type *type;	void *private;	int err; 	if (sock->state == SS_CONNECTED)		return -EINVAL; 	BUILD_BUG_ON(offsetof(struct sockaddr_alg_new, salg_name) !=		     offsetof(struct sockaddr_alg, salg_name));	BUILD_BUG_ON(offsetof(struct sockaddr_alg, salg_name) != sizeof(*sa)); 	if (addr_len < sizeof(*sa) + 1)		return -EINVAL; 	/* If caller uses non-allowed flag, return error. */	if ((sa->salg_feat & ~allowed) || (sa->salg_mask & ~allowed))		return -EINVAL; 	sa->salg_type[sizeof(sa->salg_type) - 1] = 0;	sa->salg_name[addr_len - sizeof(*sa) - 1] = 0; 	type = alg_get_type(sa->salg_type);	if (PTR_ERR(type) == -ENOENT) {		request_module("algif-%s", sa->salg_type);		type = alg_get_type(sa->salg_type);	} 	if (IS_ERR(type))		return PTR_ERR(type); 	private = type->bind(sa->salg_name);	if (IS_ERR(private)) {		module_put(type->owner);		return PTR_ERR(private);	} 	err = -EBUSY;	lock_sock(sk);	if (atomic_read(&ask->refcnt))		goto unlock; 	swap(ask->type, type);	swap(ask->private, private); 	err = 0; unlock:	release_sock(sk); 	alg_do_release(type, private); 	return err;} static int alg_setkey(struct sock *sk, sockptr_t ukey, unsigned int keylen){	struct alg_sock *ask = alg_sk(sk);	const struct af_alg_type *type = ask->type;	u8 *key;	int err; 	key = sock_kmalloc(sk, keylen, GFP_KERNEL);	if (!key)		return -ENOMEM; 	err = -EFAULT;	if (copy_from_sockptr(key, ukey, keylen))		goto out; 	err = type->setkey(ask->private, key, keylen); out:	sock_kzfree_s(sk, key, keylen); 	return err;} #ifdef CONFIG_KEYS static const u8 *key_data_ptr_user(const struct key *key,				   unsigned int *datalen){	const struct user_key_payload *ukp; 	ukp = user_key_payload_locked(key);	if (IS_ERR_OR_NULL(ukp))		return ERR_PTR(-EKEYREVOKED); 	*datalen = key->datalen; 	return ukp->data;} static const u8 *key_data_ptr_encrypted(const struct key *key,					unsigned int *datalen){	const struct encrypted_key_payload *ekp; 	ekp = dereference_key_locked(key);	if (IS_ERR_OR_NULL(ekp))		return ERR_PTR(-EKEYREVOKED); 	*datalen = ekp->decrypted_datalen; 	return ekp->decrypted_data;} static const u8 *key_data_ptr_trusted(const struct key *key,				      unsigned int *datalen){	const struct trusted_key_payload *tkp; 	tkp = dereference_key_locked(key);	if (IS_ERR_OR_NULL(tkp))		return ERR_PTR(-EKEYREVOKED); 	*datalen = tkp->key_len; 	return tkp->key;} static struct key *lookup_key(key_serial_t serial){	key_ref_t key_ref; 	key_ref = lookup_user_key(serial, 0, KEY_NEED_SEARCH);	if (IS_ERR(key_ref))		return ERR_CAST(key_ref); 	return key_ref_to_ptr(key_ref);} static int alg_setkey_by_key_serial(struct alg_sock *ask, sockptr_t optval,				    unsigned int optlen){	const struct af_alg_type *type = ask->type;	u8 *key_data = NULL;	unsigned int key_datalen;	key_serial_t serial;	struct key *key;	const u8 *ret;	int err; 	if (optlen != sizeof(serial))		return -EINVAL; 	if (copy_from_sockptr(&serial, optval, optlen))		return -EFAULT; 	key = lookup_key(serial);	if (IS_ERR(key))		return PTR_ERR(key); 	down_read(&key->sem); 	ret = ERR_PTR(-ENOPROTOOPT);	if (!strcmp(key->type->name, "user") ||	    !strcmp(key->type->name, "logon")) {		ret = key_data_ptr_user(key, &key_datalen);	} else if (IS_REACHABLE(CONFIG_ENCRYPTED_KEYS) &&			   !strcmp(key->type->name, "encrypted")) {		ret = key_data_ptr_encrypted(key, &key_datalen);	} else if (IS_REACHABLE(CONFIG_TRUSTED_KEYS) &&			   !strcmp(key->type->name, "trusted")) {		ret = key_data_ptr_trusted(key, &key_datalen);	} 	if (IS_ERR(ret)) {		up_read(&key->sem);		key_put(key);		return PTR_ERR(ret);	} 	key_data = sock_kmemdup(&ask->sk, ret, key_datalen, GFP_KERNEL);	if (!key_data) {		up_read(&key->sem);		key_put(key);		return -ENOMEM;	} 	up_read(&key->sem);	key_put(key); 	err = type->setkey(ask->private, key_data, key_datalen); 	sock_kzfree_s(&ask->sk, key_data, key_datalen); 	return err;} #else static inline int alg_setkey_by_key_serial(struct alg_sock *ask,					   sockptr_t optval,					   unsigned int optlen){	return -ENOPROTOOPT;} #endif static int alg_setsockopt(struct socket *sock, int level, int optname,			  sockptr_t optval, unsigned int optlen){	struct sock *sk = sock->sk;	struct alg_sock *ask = alg_sk(sk);	const struct af_alg_type *type;	int err = -EBUSY; 	lock_sock(sk);	if (atomic_read(&ask->refcnt) != atomic_read(&ask->nokey_refcnt))		goto unlock; 	type = ask->type; 	err = -ENOPROTOOPT;	if (level != SOL_ALG || !type)		goto unlock; 	switch (optname) {	case ALG_SET_KEY:	case ALG_SET_KEY_BY_KEY_SERIAL:		if (sock->state == SS_CONNECTED)			goto unlock;		if (!type->setkey)			goto unlock; 		if (optname == ALG_SET_KEY_BY_KEY_SERIAL)			err = alg_setkey_by_key_serial(ask, optval, optlen);		else			err = alg_setkey(sk, optval, optlen);		break;	case ALG_SET_AEAD_AUTHSIZE:		if (sock->state == SS_CONNECTED)			goto unlock;		if (!type->setauthsize)			goto unlock;		err = type->setauthsize(ask->private, optlen);		break;	case ALG_SET_DRBG_ENTROPY:		if (sock->state == SS_CONNECTED)			goto unlock;		if (!type->setentropy)			goto unlock; 		err = type->setentropy(ask->private, optval, optlen);	} unlock:	release_sock(sk); 	return err;} int af_alg_accept(struct sock *sk, struct socket *newsock,		  struct proto_accept_arg *arg){	struct alg_sock *ask = alg_sk(sk);	const struct af_alg_type *type;	struct sock *sk2;	unsigned int nokey;	int err; 	lock_sock(sk);	type = ask->type; 	err = -EINVAL;	if (!type)		goto unlock; 	sk2 = sk_alloc(sock_net(sk), PF_ALG, GFP_KERNEL, &alg_proto, arg->kern);	err = -ENOMEM;	if (!sk2)		goto unlock; 	sock_init_data(newsock, sk2);	security_sock_graft(sk2, newsock);	security_sk_clone(sk, sk2); 	/*	 * newsock->ops assigned here to allow type->accept call to override	 * them when required.	 */	newsock->ops = type->ops;	err = type->accept(ask->private, sk2); 	nokey = err == -ENOKEY;	if (nokey && type->accept_nokey)		err = type->accept_nokey(ask->private, sk2); 	if (err)

Lines 1–500 of 1316.