summaryrefslogtreecommitdiffstats
path: root/crypto/algif_skcipher.c
diff options
context:
space:
mode:
authorJinke Han <jinkehan@didiglobal.com>2026-09-08 15:37:42 +0800
committerIngo Molnar <mingo@kernel.org>2026-09-17 09:48:42 +0200
commita5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch)
tree73fef824f9a846cf935869d0dbc5070ef6e2f741 /crypto/algif_skcipher.c
downloadlinux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz
linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Signed-off-by: Jinke Han <jinkehan@didiglobal.com> Signed-off-by: Ingo Molnar <mingo@kernel.org> Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Acked-by: Yafang Shao <laoar.shao@gmail.com> Acked-by: Borislav Petkov <bp@alien8.de> Cc: Peter Zijlstra <peterz@infradead.org> Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'crypto/algif_skcipher.c')
-rw-r--r--crypto/algif_skcipher.c437
1 files changed, 437 insertions, 0 deletions
diff --git a/crypto/algif_skcipher.c b/crypto/algif_skcipher.c
new file mode 100644
index 000000000..1e61fe6e2
--- /dev/null
+++ b/crypto/algif_skcipher.c
@@ -0,0 +1,437 @@
+// 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");