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: 5+ 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
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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox