From: Leonid Ravich <lravich@amazon.com>
To: <linux-crypto@vger.kernel.org>, <dm-devel@lists.linux.dev>
Cc: <linux-block@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
<herbert@gondor.apana.org.au>, <davem@davemloft.net>,
<ebiggers@kernel.org>, <snitzer@kernel.org>,
<mpatocka@redhat.com>, <axboe@kernel.dk>
Subject: [PATCH v5 2/5] crypto: dun - data-unit-number dispatch template
Date: Tue, 30 Jun 2026 08:34:28 +0000 [thread overview]
Message-ID: <20260630083431.2772-3-lravich@amazon.com> (raw)
In-Reply-To: <20260630083431.2772-1-lravich@amazon.com>
Add a dun(...) skcipher template that wraps an inner skcipher whose IV
is a wide data-unit-number counter (e.g. dun(xts(aes),le)). When the
caller sets skcipher_request::data_unit_size, the template splits the
request into cryptlen / data_unit_size sub-requests on the inner cipher,
walking the IV +1 per unit. Each inner ->encrypt/->decrypt is a direct
call, so only the outer dispatch into the crypto API is indirect -- the
per-unit work is not.
The second template parameter selects the counter endianness: dun(...,le)
for a little-endian counter (dm-crypt plain64, blk-crypto inline
encryption) and dun(...,be) for a big-endian one (dm-crypt plain64be).
Those are the only two ways a per-unit IV relates to its neighbour by a
+1 step; IV modes that are not such a counter are simply not wrapped.
Like cryptd() and pcrypt(), dun() wraps an inner skcipher and changes
only how the request is dispatched -- here, split across data units --
performing no cipher transform of its own.
A dun() tfm exists solely for multi-DU dispatch, so a request with
data_unit_size 0 is rejected with -EINVAL; a caller wanting plain
single-DU encryption uses the inner skcipher.
A hardware engine that consumes a whole multi-DU request in one pass
registers its own dun(...) at a higher cra_priority and is selected
automatically by the existing priority mechanism; no per-algorithm
capability flag is needed. The generic template is sync-only (the split
loop treats any non-zero inner return as terminal), so it resolves against
a sync inner cipher (mask | CRYPTO_ALG_ASYNC); async is left to such
native drivers.
The inner IV must be a whole number of 64-bit limbs and no wider than 32
bytes: 16 covers xts(...), 32 covers the widest inline-encryption mode
(Adiantum).
Suggested-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: Leonid Ravich <lravich@amazon.com>
---
crypto/Kconfig | 14 ++
crypto/Makefile | 1 +
crypto/dun.c | 359 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 374 insertions(+)
create mode 100644 crypto/dun.c
diff --git a/crypto/Kconfig b/crypto/Kconfig
index 103d1f58cb7c..4f90a780c4fc 100644
--- a/crypto/Kconfig
+++ b/crypto/Kconfig
@@ -746,6 +746,20 @@ config CRYPTO_XTS
implementation currently can't handle a sectorsize which is not a
multiple of 16 bytes.
+config CRYPTO_DUN
+ tristate "Data-unit-number (DUN) dispatch template"
+ select CRYPTO_SKCIPHER
+ select CRYPTO_MANAGER
+ help
+ dun(...) wraps an skcipher whose IV is a wide data-unit-number
+ counter (e.g. xts(aes)) and lets a caller submit several data units
+ sharing one starting IV in a single request, via
+ skcipher_request::data_unit_size. The counter endianness is the
+ second parameter: dun(xts(aes),le) or dun(xts(aes),be). The template
+ splits the request into one inner call per data unit; a hardware
+ driver may register a higher-priority dun(...) that handles the whole
+ request in one pass. The first user is dm-crypt.
+
endmenu
menu "AEAD (authenticated encryption with associated data) ciphers"
diff --git a/crypto/Makefile b/crypto/Makefile
index 162242593c7c..584d9e8c4347 100644
--- a/crypto/Makefile
+++ b/crypto/Makefile
@@ -93,6 +93,7 @@ obj-$(CONFIG_CRYPTO_PCBC) += pcbc.o
obj-$(CONFIG_CRYPTO_CTS) += cts.o
obj-$(CONFIG_CRYPTO_LRW) += lrw.o
obj-$(CONFIG_CRYPTO_XTS) += xts.o
+obj-$(CONFIG_CRYPTO_DUN) += dun.o
obj-$(CONFIG_CRYPTO_CTR) += ctr.o
obj-$(CONFIG_CRYPTO_XCTR) += xctr.o
obj-$(CONFIG_CRYPTO_HCTR2) += hctr2.o
diff --git a/crypto/dun.c b/crypto/dun.c
new file mode 100644
index 000000000000..4fcb81a025b9
--- /dev/null
+++ b/crypto/dun.c
@@ -0,0 +1,359 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * dun: data-unit-number dispatch template for skcipher
+ *
+ * Wraps an inner skcipher (e.g. xts(aes)) and, when the caller sets
+ * skcipher_request::data_unit_size, splits the request into cryptlen /
+ * data_unit_size sub-requests, each unit's IV the previous one +1 -- the
+ * data-unit-number (DUN) convention. The second parameter selects the IV
+ * walk (see struct dun_mode): dun(xts(aes),le) or dun(xts(aes),be).
+ *
+ * Like cryptd()/pcrypt(), dun() only changes how a request is dispatched and
+ * performs no transform of its own; a native one-pass multi-DU driver wins by
+ * cra_priority. Callers that never set data_unit_size pay nothing.
+ */
+
+#include <crypto/algapi.h>
+#include <crypto/internal/skcipher.h>
+#include <crypto/scatterwalk.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/string.h>
+
+/* Bounds the on-stack IV buffers: 16 covers xts(...), 32 covers Adiantum. */
+#define DUN_MAX_IVSIZE 32
+
+/*
+ * A dun() mode is the rule for deriving each data unit's IV from the request's
+ * starting IV. @name is the template's second parameter; @iv_next advances the
+ * @ivsize-byte @iv in place to the next data unit. @ivsize_ok rejects IV sizes
+ * the walk can't handle. Add a row to dun_modes[] to support a new convention.
+ */
+struct dun_mode {
+ const char *name;
+ void (*iv_next)(u8 *iv, unsigned int ivsize);
+ bool (*ivsize_ok)(unsigned int ivsize);
+};
+
+struct dun_tfm_ctx {
+ struct crypto_skcipher *child;
+ const struct dun_mode *mode;
+};
+
+struct dun_inst_ctx {
+ struct crypto_skcipher_spawn spawn;
+ const struct dun_mode *mode;
+};
+
+struct dun_request_ctx {
+ /* Must be last; the child request is appended with its own reqsize. */
+ struct skcipher_request subreq;
+};
+
+/* Little-endian counter: increment the IV per __le64 limb, low limb first. */
+static void dun_iv_next_le(u8 *iv, unsigned int ivsize)
+{
+ unsigned int i;
+
+ for (i = 0; i < ivsize; i += sizeof(__le64)) {
+ __le64 limb;
+ u64 v;
+
+ memcpy(&limb, iv + i, sizeof(limb));
+ v = le64_to_cpu(limb) + 1;
+ limb = cpu_to_le64(v);
+ memcpy(iv + i, &limb, sizeof(limb));
+ if (likely(v != 0))
+ break; /* no carry into the next limb */
+ }
+}
+
+/* Big-endian counter: increment the IV byte-wise from the last byte. */
+static void dun_iv_next_be(u8 *iv, unsigned int ivsize)
+{
+ unsigned int i = ivsize;
+
+ while (i--) {
+ if (likely(++iv[i]))
+ break; /* no carry into the next byte */
+ }
+}
+
+/*
+ * le requires this: it walks the IV in __le64 limbs, so the size must be a
+ * whole number of limbs. be increments byte-wise and would accept any size,
+ * but reuses the same check for a uniform value-space.
+ */
+static bool dun_ivsize_whole_limbs(unsigned int ivsize)
+{
+ return IS_ALIGNED(ivsize, sizeof(__le64));
+}
+
+static const struct dun_mode dun_modes[] = {
+ { "le", dun_iv_next_le, dun_ivsize_whole_limbs },
+ { "be", dun_iv_next_be, dun_ivsize_whole_limbs },
+};
+
+static const struct dun_mode *dun_find_mode(const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(dun_modes); i++)
+ if (!strcmp(name, dun_modes[i].name))
+ return &dun_modes[i];
+ return NULL;
+}
+
+static int dun_setkey(struct crypto_skcipher *parent, const u8 *key,
+ unsigned int keylen)
+{
+ struct dun_tfm_ctx *ctx = crypto_skcipher_ctx(parent);
+ struct crypto_skcipher *child = ctx->child;
+
+ crypto_skcipher_clear_flags(child, CRYPTO_TFM_REQ_MASK);
+ crypto_skcipher_set_flags(child, crypto_skcipher_get_flags(parent) &
+ CRYPTO_TFM_REQ_MASK);
+ return crypto_skcipher_setkey(child, key, keylen);
+}
+
+/*
+ * Run one inner ->crypt per data unit, walking the IV as a wide counter.
+ * @req->iv is never modified; the inner cipher only sees the iv_unit copy.
+ */
+static int dun_split(struct skcipher_request *req,
+ int (*crypt)(struct skcipher_request *))
+{
+ struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
+ struct dun_tfm_ctx *ctx = crypto_skcipher_ctx(tfm);
+ struct dun_request_ctx *rctx = skcipher_request_ctx(req);
+ struct skcipher_request *subreq = &rctx->subreq;
+ const unsigned int du = req->data_unit_size;
+ const unsigned int total = req->cryptlen;
+ const unsigned int ivsize = crypto_skcipher_ivsize(tfm);
+ const struct dun_mode *mode = ctx->mode;
+ bool inplace = req->src == req->dst;
+ struct scatter_walk src_walk, dst_walk;
+ struct scatterlist src_sg[2], dst_sg[2];
+ u8 iv_ctr[DUN_MAX_IVSIZE];
+ u8 iv_unit[DUN_MAX_IVSIZE];
+ unsigned int off;
+ int err = 0;
+
+ /* iv_ctr is the counter; iv_unit is a per-unit copy an inner may write
+ * back in place (e.g. xts, essiv), so the counter is never mutated.
+ */
+ memcpy(iv_ctr, req->iv, ivsize);
+
+ sg_init_table(src_sg, 2);
+ scatterwalk_start(&src_walk, req->src);
+ if (!inplace) {
+ sg_init_table(dst_sg, 2);
+ scatterwalk_start(&dst_walk, req->dst);
+ }
+
+ skcipher_request_set_tfm(subreq, ctx->child);
+ skcipher_request_set_callback(subreq, skcipher_request_flags(req),
+ NULL, NULL);
+
+ for (off = 0; off < total; off += du) {
+ struct scatterlist *s, *d;
+
+ scatterwalk_get_sglist(&src_walk, src_sg);
+ scatterwalk_skip(&src_walk, du);
+ s = src_sg;
+ if (inplace) {
+ d = src_sg;
+ } else {
+ scatterwalk_get_sglist(&dst_walk, dst_sg);
+ scatterwalk_skip(&dst_walk, du);
+ d = dst_sg;
+ }
+
+ memcpy(iv_unit, iv_ctr, ivsize);
+ skcipher_request_set_crypt(subreq, s, d, du, iv_unit);
+ err = crypt(subreq);
+ if (err)
+ break;
+
+ mode->iv_next(iv_ctr, ivsize);
+ }
+
+ return err;
+}
+
+/*
+ * Validate a multi-DU request: non-zero cryptlen, and a data_unit_size that is
+ * set, a multiple of the block size, and divides cryptlen evenly.
+ */
+static int dun_check(struct skcipher_request *req)
+{
+ struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
+
+ if (!req->cryptlen || !req->data_unit_size ||
+ !IS_ALIGNED(req->data_unit_size, crypto_skcipher_blocksize(tfm)) ||
+ (req->cryptlen % req->data_unit_size))
+ return -EINVAL;
+ return 0;
+}
+
+static int dun_encrypt(struct skcipher_request *req)
+{
+ int err = dun_check(req);
+
+ if (err)
+ return err;
+ return dun_split(req, crypto_skcipher_encrypt);
+}
+
+static int dun_decrypt(struct skcipher_request *req)
+{
+ int err = dun_check(req);
+
+ if (err)
+ return err;
+ return dun_split(req, crypto_skcipher_decrypt);
+}
+
+static int dun_init_tfm(struct crypto_skcipher *tfm)
+{
+ struct skcipher_instance *inst = skcipher_alg_instance(tfm);
+ struct dun_inst_ctx *ictx = skcipher_instance_ctx(inst);
+ struct dun_tfm_ctx *ctx = crypto_skcipher_ctx(tfm);
+ struct crypto_skcipher *child;
+
+ child = crypto_spawn_skcipher(&ictx->spawn);
+ if (IS_ERR(child))
+ return PTR_ERR(child);
+
+ ctx->child = child;
+ ctx->mode = ictx->mode;
+ crypto_skcipher_set_reqsize(tfm,
+ sizeof(struct dun_request_ctx) +
+ crypto_skcipher_reqsize(child));
+ return 0;
+}
+
+static void dun_exit_tfm(struct crypto_skcipher *tfm)
+{
+ struct dun_tfm_ctx *ctx = crypto_skcipher_ctx(tfm);
+
+ crypto_free_skcipher(ctx->child);
+}
+
+static void dun_free_instance(struct skcipher_instance *inst)
+{
+ struct dun_inst_ctx *ictx = skcipher_instance_ctx(inst);
+
+ crypto_drop_skcipher(&ictx->spawn);
+ kfree(inst);
+}
+
+static int dun_create(struct crypto_template *tmpl, struct rtattr **tb)
+{
+ struct skcipher_alg_common *alg;
+ struct skcipher_instance *inst;
+ struct dun_inst_ctx *ictx;
+ const struct dun_mode *mode;
+ const char *cipher_name;
+ const char *mode_name;
+ u32 mask;
+ int err;
+
+ err = crypto_check_attr_type(tb, CRYPTO_ALG_TYPE_SKCIPHER, &mask);
+ if (err)
+ return err;
+
+ cipher_name = crypto_attr_alg_name(tb[1]);
+ if (IS_ERR(cipher_name))
+ return PTR_ERR(cipher_name);
+
+ /* Second parameter: the IV-walk mode (see dun_modes[]). */
+ mode_name = crypto_attr_alg_name(tb[2]);
+ if (IS_ERR(mode_name))
+ return PTR_ERR(mode_name);
+ mode = dun_find_mode(mode_name);
+ if (!mode)
+ return -EINVAL;
+
+ inst = kzalloc(sizeof(*inst) + sizeof(*ictx), GFP_KERNEL);
+ if (!inst)
+ return -ENOMEM;
+ ictx = skcipher_instance_ctx(inst);
+ ictx->mode = mode;
+
+ /*
+ * Sync-only: the split loop can't drive an async (-EINPROGRESS) child,
+ * so resolve against a sync inner (mask | CRYPTO_ALG_ASYNC).
+ */
+ err = crypto_grab_skcipher(&ictx->spawn, skcipher_crypto_instance(inst),
+ cipher_name, 0, mask | CRYPTO_ALG_ASYNC);
+ if (err)
+ goto err_free_inst;
+
+ alg = crypto_spawn_skcipher_alg_common(&ictx->spawn);
+
+ /* The mode must accept this IV size, and it must fit our buffers. */
+ err = -EINVAL;
+ if (!alg->ivsize || alg->ivsize > DUN_MAX_IVSIZE ||
+ !mode->ivsize_ok(alg->ivsize))
+ goto err_free_inst;
+
+ err = -ENAMETOOLONG;
+ if (snprintf(inst->alg.base.cra_name, CRYPTO_MAX_ALG_NAME, "dun(%s,%s)",
+ alg->base.cra_name, mode->name) >= CRYPTO_MAX_ALG_NAME)
+ goto err_free_inst;
+ if (snprintf(inst->alg.base.cra_driver_name, CRYPTO_MAX_ALG_NAME,
+ "dun(%s,%s)", alg->base.cra_driver_name,
+ mode->name) >= CRYPTO_MAX_ALG_NAME)
+ goto err_free_inst;
+
+ inst->alg.base.cra_priority = alg->base.cra_priority;
+ inst->alg.base.cra_blocksize = alg->base.cra_blocksize;
+ inst->alg.base.cra_alignmask = alg->base.cra_alignmask;
+ inst->alg.base.cra_ctxsize = sizeof(struct dun_tfm_ctx);
+
+ inst->alg.ivsize = alg->ivsize;
+ inst->alg.chunksize = alg->chunksize;
+ inst->alg.min_keysize = alg->min_keysize;
+ inst->alg.max_keysize = alg->max_keysize;
+
+ inst->alg.init = dun_init_tfm;
+ inst->alg.exit = dun_exit_tfm;
+ inst->alg.setkey = dun_setkey;
+ inst->alg.encrypt = dun_encrypt;
+ inst->alg.decrypt = dun_decrypt;
+
+ inst->free = dun_free_instance;
+
+ err = skcipher_register_instance(tmpl, inst);
+ if (err) {
+err_free_inst:
+ dun_free_instance(inst);
+ }
+ return err;
+}
+
+static struct crypto_template dun_tmpl = {
+ .name = "dun",
+ .create = dun_create,
+ .module = THIS_MODULE,
+};
+
+static int __init dun_module_init(void)
+{
+ return crypto_register_template(&dun_tmpl);
+}
+
+static void __exit dun_module_exit(void)
+{
+ crypto_unregister_template(&dun_tmpl);
+}
+
+module_init(dun_module_init);
+module_exit(dun_module_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Data-unit-number dispatch template for skcipher");
+MODULE_ALIAS_CRYPTO("dun");
--
2.47.3
next prev parent reply other threads:[~2026-06-30 8:34 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-30 8:34 [PATCH v5 0/5] crypto: skcipher - multi-data-unit dispatch as a template Leonid Ravich
2026-06-30 8:34 ` [PATCH v5 1/5] crypto: skcipher - add per-request data_unit_size Leonid Ravich
2026-06-30 8:34 ` Leonid Ravich [this message]
2026-06-30 8:34 ` [PATCH v5 3/5] crypto: testmgr - test dun() dispatch Leonid Ravich
2026-06-30 8:34 ` [PATCH v5 4/5] dm crypt: batch a bio segment's sectors via dun() Leonid Ravich
2026-07-01 6:53 ` [PATCH v5 5/5] blk-crypto: fallback - batch a segment's data units " Leonid Ravich
2026-07-01 7:19 ` [PATCH v5 0/5] crypto: skcipher - multi-data-unit dispatch as a template Eric Biggers
2026-07-02 8:45 ` Leonid Ravich
2026-07-02 16:45 ` Eric Biggers
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260630083431.2772-3-lravich@amazon.com \
--to=lravich@amazon.com \
--cc=axboe@kernel.dk \
--cc=davem@davemloft.net \
--cc=dm-devel@lists.linux.dev \
--cc=ebiggers@kernel.org \
--cc=herbert@gondor.apana.org.au \
--cc=linux-block@vger.kernel.org \
--cc=linux-crypto@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mpatocka@redhat.com \
--cc=snitzer@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.