summaryrefslogtreecommitdiff
path: root/crypto
diff options
context:
space:
mode:
authorEric Biggers <ebiggers@kernel.org>2026-06-22 16:48:03 -0700
committerHerbert Xu <herbert@gondor.apana.org.au>2026-07-05 13:27:15 +0800
commit2f204fe718f5bf519013cc2536ad7bb2cbb51661 (patch)
tree1bc90d994eb90f94aff90aae59b3a01e4a50db33 /crypto
parent7e28b0a5c4b7d075b98ce6d8f5290a9d3deb5b92 (diff)
downloadlinux-2f204fe718f5bf519013cc2536ad7bb2cbb51661.tar.gz
linux-2f204fe718f5bf519013cc2536ad7bb2cbb51661.zip
crypto: af_alg - Add af_alg_restrict sysctl, defaulting to 1
AF_ALG is a frequent source of vulnerabilities and a maintenance nightmare. It exposes far more functionality to userspace than ever should have been exposed, especially to unprivileged processes. Recent exploits have targeted kernel internal implementation details like "authencesn" that have zero use case for userspace access. Fortunately, AF_ALG is rarely used in practice, as userspace crypto libraries exist. And when it is used, only some functionality is known to be used, and many users are known to hold capabilities already. iwd for example requires CAP_NET_ADMIN and has a known algorithm list (https://lore.kernel.org/linux-crypto/bcbbef00-5881-421b-8892-7be6c04b832d@gmail.com/). Thus, let's restrict the set of allowed algorithms by default, depending on the capabilities held. Add a sysctl /proc/sys/crypto/af_alg_restrict with meaning: 0: unrestricted 1: limited functionality 2: completely disabled Set the default value to 1, which enables an algorithm allowlist for unprivileged processes and a slightly longer allowlist for privileged processes. Note that the list may be tweaked in the future. However, the common use cases such as iwd and bluez are taken into account already. I've tested that iwd still works with the default value of 1. Signed-off-by: Eric Biggers <ebiggers@kernel.org> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Diffstat (limited to 'crypto')
-rw-r--r--crypto/af_alg.c72
-rw-r--r--crypto/algif_aead.c11
-rw-r--r--crypto/algif_hash.c24
-rw-r--r--crypto/algif_rng.c9
-rw-r--r--crypto/algif_skcipher.c20
5 files changed, 130 insertions, 6 deletions
diff --git a/crypto/af_alg.c b/crypto/af_alg.c
index cce000e8590e..34b801568fba 100644
--- a/crypto/af_alg.c
+++ b/crypto/af_alg.c
@@ -8,6 +8,7 @@
*/
#include <linux/atomic.h>
+#include <linux/capability.h>
#include <crypto/if_alg.h>
#include <linux/crypto.h>
#include <linux/init.h>
@@ -22,10 +23,28 @@
#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;
@@ -110,6 +129,39 @@ int af_alg_unregister_type(const struct af_alg_type *type)
}
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 &&
+ (!ent->privileged || af_alg_capable()))
+ return 0;
+ }
+ }
+ /*
+ * 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)
@@ -506,6 +558,9 @@ static int alg_create(struct net *net, struct socket *sock, int protocol,
struct sock *sk;
int err;
+ if (READ_ONCE(af_alg_restrict) == 2)
+ return -EAFNOSUPPORT;
+
if (sock->type != SOCK_SEQPACKET)
return -ESOCKTNOSUPPORT;
if (protocol != 0)
@@ -1222,27 +1277,32 @@ EXPORT_SYMBOL_GPL(af_alg_get_rsgl);
static int __init af_alg_init(void)
{
- int err = proto_register(&alg_proto, 0);
+ int err;
+
+ af_alg_header = register_sysctl("crypto", af_alg_table);
+ err = proto_register(&alg_proto, 0);
if (err)
- goto out;
+ goto out_unregister_sysctl;
err = sock_register(&alg_family);
- if (err != 0)
+ if (err)
goto out_unregister_proto;
-out:
- return err;
+ return 0;
out_unregister_proto:
proto_unregister(&alg_proto);
- goto out;
+out_unregister_sysctl:
+ unregister_sysctl_table(af_alg_header);
+ return err;
}
static void __exit af_alg_exit(void)
{
sock_unregister(PF_ALG);
proto_unregister(&alg_proto);
+ unregister_sysctl_table(af_alg_header);
}
module_init(af_alg_init);
diff --git a/crypto/algif_aead.c b/crypto/algif_aead.c
index 787aac8aeb24..b9217f9086aa 100644
--- a/crypto/algif_aead.c
+++ b/crypto/algif_aead.c
@@ -34,6 +34,11 @@
#include <linux/net.h>
#include <net/sock.h>
+static const struct af_alg_allowlist_entry aead_allowlist[] = {
+ { "ccm(aes)", true }, /* bluez */
+ {},
+};
+
static inline bool aead_sufficient_data(struct sock *sk)
{
struct alg_sock *ask = alg_sk(sk);
@@ -344,6 +349,12 @@ static struct proto_ops algif_aead_ops_nokey = {
static void *aead_bind(const char *name)
{
+ int err;
+
+ err = af_alg_check_restriction(name, aead_allowlist);
+ if (err)
+ return ERR_PTR(err);
+
return crypto_alloc_aead(name, 0, AF_ALG_CRYPTOAPI_MASK);
}
diff --git a/crypto/algif_hash.c b/crypto/algif_hash.c
index 5452ad6c1506..a8d958d51ece 100644
--- a/crypto/algif_hash.c
+++ b/crypto/algif_hash.c
@@ -16,6 +16,24 @@
#include <linux/net.h>
#include <net/sock.h>
+static const struct af_alg_allowlist_entry hash_allowlist[] = {
+ { "cmac(aes)", true }, /* iwd, bluez */
+ { "hmac(md5)", true }, /* iwd */
+ { "hmac(sha1)", true }, /* iwd */
+ { "hmac(sha224)", true }, /* iwd */
+ { "hmac(sha256)", true }, /* iwd */
+ { "hmac(sha384)", true }, /* iwd */
+ { "hmac(sha512)", true }, /* iwd, sha512hmac */
+ { "md4", true }, /* iwd */
+ { "md5", true }, /* iwd */
+ { "sha1", false }, /* iwd, iproute2 < 7.0 */
+ { "sha224", true }, /* iwd */
+ { "sha256", true }, /* iwd */
+ { "sha384", true }, /* iwd */
+ { "sha512", true }, /* iwd */
+ {},
+};
+
struct hash_ctx {
struct af_alg_sgl sgl;
@@ -382,6 +400,12 @@ static struct proto_ops algif_hash_ops_nokey = {
static void *hash_bind(const char *name)
{
+ int err;
+
+ err = af_alg_check_restriction(name, hash_allowlist);
+ if (err)
+ return ERR_PTR(err);
+
return crypto_alloc_ahash(name, 0, AF_ALG_CRYPTOAPI_MASK);
}
diff --git a/crypto/algif_rng.c b/crypto/algif_rng.c
index 4dfe7899f8fa..bd522915d56d 100644
--- a/crypto/algif_rng.c
+++ b/crypto/algif_rng.c
@@ -50,6 +50,10 @@ MODULE_LICENSE("GPL");
MODULE_AUTHOR("Stephan Mueller <smueller@chronox.de>");
MODULE_DESCRIPTION("User-space interface for random number generators");
+static const struct af_alg_allowlist_entry rng_allowlist[] = {
+ {},
+};
+
struct rng_ctx {
#define MAXSIZE 128
unsigned int len;
@@ -201,6 +205,11 @@ static void *rng_bind(const char *name)
{
struct rng_parent_ctx *pctx;
struct crypto_rng *rng;
+ int err;
+
+ err = af_alg_check_restriction(name, rng_allowlist);
+ if (err)
+ return ERR_PTR(err);
pctx = kzalloc_obj(*pctx);
if (!pctx)
diff --git a/crypto/algif_skcipher.c b/crypto/algif_skcipher.c
index df20bdfe1f1f..2b8069667974 100644
--- a/crypto/algif_skcipher.c
+++ b/crypto/algif_skcipher.c
@@ -34,6 +34,20 @@
#include <linux/net.h>
#include <net/sock.h>
+static const struct af_alg_allowlist_entry skcipher_allowlist[] = {
+ { "adiantum(xchacha12,aes)", false }, /* cryptsetup */
+ { "adiantum(xchacha20,aes)", false }, /* cryptsetup */
+ { "cbc(aes)", true }, /* iwd */
+ { "cbc(des)", true }, /* iwd */
+ { "cbc(des3_ede)", true }, /* iwd */
+ { "ctr(aes)", true }, /* iwd */
+ { "ecb(aes)", true }, /* iwd, bluez */
+ { "ecb(des)", true }, /* iwd */
+ { "hctr2(aes)", false }, /* cryptsetup */
+ { "xts(aes)", false }, /* cryptsetup benchmark */
+ {},
+};
+
static int skcipher_sendmsg(struct socket *sock, struct msghdr *msg,
size_t size)
{
@@ -309,6 +323,12 @@ static struct proto_ops algif_skcipher_ops_nokey = {
static void *skcipher_bind(const char *name)
{
+ int err;
+
+ err = af_alg_check_restriction(name, skcipher_allowlist);
+ if (err)
+ return ERR_PTR(err);
+
return crypto_alloc_skcipher(name, 0, AF_ALG_CRYPTOAPI_MASK);
}