Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions crypto/skcipher.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <linux/cryptouser.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/log2.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/seq_file.h>
Expand Down Expand Up @@ -432,15 +433,139 @@ int crypto_skcipher_setkey(struct crypto_skcipher *tfm, const u8 *key,
}
EXPORT_SYMBOL_GPL(crypto_skcipher_setkey);

/* IV size for the 128-bit LE-counter multi-data-unit convention. */
#define SKCIPHER_MDU_IVSIZE 16

static inline void skcipher_iv_inc_le128(u8 *iv)
{
__le64 lo_le, hi_le;
u64 lo;

memcpy(&lo_le, iv, 8);
memcpy(&hi_le, iv + 8, 8);
lo = le64_to_cpu(lo_le) + 1;
lo_le = cpu_to_le64(lo);
memcpy(iv, &lo_le, 8);
if (unlikely(lo == 0)) {
hi_le = cpu_to_le64(le64_to_cpu(hi_le) + 1);
memcpy(iv + 8, &hi_le, 8);
}
}

/*
* Dispatch a multi-data-unit request as one single-DU sub-request per
* unit. Each unit's IV is the caller's IV plus the unit index, taken
* as a 128-bit little-endian counter. A pair of scatter_walks advances
* through src/dst in a single linear pass (O(entries + units)); building
* each sub-request's view with scatterwalk_ffwd() would instead rescan
* from the head every unit, i.e. O(units^2).
*/
static int skcipher_split_data_units(struct skcipher_request *req,
int (*body)(struct skcipher_request *))
{
const unsigned int du = req->data_unit_size;
const unsigned int total = req->cryptlen;
struct scatterlist *orig_src = req->src;
struct scatterlist *orig_dst = req->dst;
bool inplace = orig_src == orig_dst;
struct scatter_walk src_walk, dst_walk;
struct scatterlist src_sg[2], dst_sg[2];
u8 iv_orig[SKCIPHER_MDU_IVSIZE];
u8 iv_work[SKCIPHER_MDU_IVSIZE];
unsigned int off;
int err = 0;

memcpy(iv_orig, req->iv, sizeof(iv_orig));
memcpy(iv_work, iv_orig, sizeof(iv_orig));

sg_init_table(src_sg, 2);
scatterwalk_start(&src_walk, orig_src);
if (!inplace) {
sg_init_table(dst_sg, 2);
scatterwalk_start(&dst_walk, orig_dst);
}

/* Stop the per-DU body from re-entering the splitter. */
req->data_unit_size = 0;
req->src = src_sg;
req->dst = inplace ? src_sg : dst_sg;

for (off = 0; off < total; off += du) {
req->cryptlen = du;
scatterwalk_get_sglist(&src_walk, src_sg);
scatterwalk_skip(&src_walk, du);
if (!inplace) {
scatterwalk_get_sglist(&dst_walk, dst_sg);
scatterwalk_skip(&dst_walk, du);
}

err = body(req);
if (err)
break;

skcipher_iv_inc_le128(iv_work);
memcpy(req->iv, iv_work, sizeof(iv_work));
}

/* Caller-visible IV is the starting IV regardless of outcome. */
memcpy(req->iv, iv_orig, sizeof(iv_orig));
req->src = orig_src;
req->dst = orig_dst;
req->cryptlen = total;
req->data_unit_size = du;
return err;
}

static int crypto_skcipher_validate_multi_du(struct skcipher_request *req)
{
const unsigned int du = req->data_unit_size;
struct crypto_skcipher *tfm;
struct skcipher_alg *alg;
u32 cra_flags;

if (likely(!du))
return 0;
if (!is_power_of_2(du) || du < SKCIPHER_MDU_IVSIZE)
return -EINVAL;
if (!req->cryptlen || (req->cryptlen & (du - 1)))
return -EINVAL;

tfm = crypto_skcipher_reqtfm(req);
alg = crypto_skcipher_alg(tfm);

/* lskcipher's *_sg path doesn't honour data_unit_size. */
if (alg->co.base.cra_type != &crypto_skcipher_type)
return -EOPNOTSUPP;

/* Capability mismatch, not a malformed request: report -EOPNOTSUPP. */
if (crypto_skcipher_ivsize(tfm) != SKCIPHER_MDU_IVSIZE)
return -EOPNOTSUPP;

/* The auto-splitter is sync-only; native drivers own async dispatch. */
cra_flags = alg->co.base.cra_flags;
if ((cra_flags & CRYPTO_ALG_ASYNC) &&
!(cra_flags & CRYPTO_ALG_SKCIPHER_NATIVE_MULTI_DU))
return -EOPNOTSUPP;

return 0;
}

int crypto_skcipher_encrypt(struct skcipher_request *req)
{
struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
struct skcipher_alg *alg = crypto_skcipher_alg(tfm);
int err;

if (crypto_skcipher_get_flags(tfm) & CRYPTO_TFM_NEED_KEY)
return -ENOKEY;
err = crypto_skcipher_validate_multi_du(req);
if (err)
return err;
if (alg->co.base.cra_type != &crypto_skcipher_type)
return crypto_lskcipher_encrypt_sg(req);
if (req->data_unit_size &&
!(alg->co.base.cra_flags & CRYPTO_ALG_SKCIPHER_NATIVE_MULTI_DU))
return skcipher_split_data_units(req, alg->encrypt);
return alg->encrypt(req);
}
EXPORT_SYMBOL_GPL(crypto_skcipher_encrypt);
Expand All @@ -449,11 +574,18 @@ int crypto_skcipher_decrypt(struct skcipher_request *req)
{
struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
struct skcipher_alg *alg = crypto_skcipher_alg(tfm);
int err;

if (crypto_skcipher_get_flags(tfm) & CRYPTO_TFM_NEED_KEY)
return -ENOKEY;
err = crypto_skcipher_validate_multi_du(req);
if (err)
return err;
if (alg->co.base.cra_type != &crypto_skcipher_type)
return crypto_lskcipher_decrypt_sg(req);
if (req->data_unit_size &&
!(alg->co.base.cra_flags & CRYPTO_ALG_SKCIPHER_NATIVE_MULTI_DU))
return skcipher_split_data_units(req, alg->decrypt);
return alg->decrypt(req);
}
EXPORT_SYMBOL_GPL(crypto_skcipher_decrypt);
Expand Down
192 changes: 192 additions & 0 deletions crypto/testmgr.c
Original file line number Diff line number Diff line change
Expand Up @@ -3211,6 +3211,194 @@ static int test_skcipher(int enc, const struct cipher_test_suite *suite,
return 0;
}

/* Increment a 16-byte IV as a little-endian 128-bit counter. */
static void test_mdu_iv_inc(u8 iv[16])
{
int i;

for (i = 0; i < 16; i++)
if (++iv[i])
break;
}

/*
* Encrypt one du_size block with a plain single-DU request; used to
* build an independent reference for the batched dispatch.
*/
static int test_mdu_ref_encrypt(struct crypto_skcipher *tfm, const u8 *in,
u8 *out, unsigned int du_size, const u8 iv[16])
{
struct skcipher_request *req;
struct scatterlist sg_in, sg_out;
DECLARE_CRYPTO_WAIT(wait);
u8 ivbuf[16];
int err;

req = skcipher_request_alloc(tfm, GFP_KERNEL);
if (!req)
return -ENOMEM;
memcpy(ivbuf, iv, 16);
memcpy(out, in, du_size);
sg_init_one(&sg_in, out, du_size);
skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
CRYPTO_TFM_REQ_MAY_SLEEP,
crypto_req_done, &wait);
skcipher_request_set_crypt(req, &sg_in, &sg_in, du_size, ivbuf);
err = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);
skcipher_request_free(req);
return err;
}

/*
* Build a deliberately fragmented SG over @buf: entries that do not
* align to du_size, so the splitter's per-DU views cross SG entries
* and exercise the scatter_walk cursor.
*/
static void test_mdu_sg_fragment(struct scatterlist *sg, unsigned int nents,
u8 *buf, unsigned int total)
{
unsigned int chunk = total / nents;
unsigned int off = 0, i;

sg_init_table(sg, nents);
for (i = 0; i < nents; i++) {
unsigned int len = (i == nents - 1) ? total - off : chunk;

sg_set_buf(&sg[i], buf + off, len);
off += len;
}
}

/*
* Multi-DU test: verify the batched dispatch produces byte-identical
* ciphertext to an independent N x single-DU reference with per-DU IVs
* walked as a 128-bit LE counter (pins the IV convention, not just
* enc/dec symmetry), over a fragmented SG, then round-trips. Real
* mismatches return -EBADMSG; ineligible algorithms skip via the
* validator's -EOPNOTSUPP.
*/
#define TEST_MDU_NR_UNITS 4
#define TEST_MDU_NR_FRAGS 5
static int test_skcipher_multi_du_one(struct crypto_skcipher *tfm,
unsigned int du_size)
{
const char *driver = crypto_skcipher_driver_name(tfm);
const unsigned int total = du_size * TEST_MDU_NR_UNITS;
struct skcipher_request *req = NULL;
struct scatterlist sg[TEST_MDU_NR_FRAGS];
DECLARE_CRYPTO_WAIT(wait);
u8 iv_orig[16], iv_work[16], iv_ref[16];
u8 *plain = NULL, *buf = NULL, *ref = NULL;
unsigned int u;
int err;

plain = kmalloc(total, GFP_KERNEL);
buf = kmalloc(total, GFP_KERNEL);
ref = kmalloc(total, GFP_KERNEL);
req = skcipher_request_alloc(tfm, GFP_KERNEL);
if (!plain || !buf || !ref || !req) {
err = -ENOMEM;
goto out;
}

get_random_bytes(plain, total);
get_random_bytes(iv_orig, sizeof(iv_orig));

/* Reference: per-DU single requests with LE128-walked IVs. */
memcpy(iv_ref, iv_orig, sizeof(iv_orig));
for (u = 0; u < TEST_MDU_NR_UNITS; u++) {
err = test_mdu_ref_encrypt(tfm, plain + u * du_size,
ref + u * du_size, du_size, iv_ref);
/* First single-DU call reveals an ineligible algorithm. */
if (err == -EOPNOTSUPP && u == 0)
goto out;
if (err) {
pr_err("alg: skcipher: %s multi-DU ref encrypt failed (du=%u): %d\n",
driver, du_size, err);
goto out;
}
test_mdu_iv_inc(iv_ref);
}

/* Batched: one request over a fragmented SG. */
memcpy(buf, plain, total);
memcpy(iv_work, iv_orig, sizeof(iv_orig));
test_mdu_sg_fragment(sg, TEST_MDU_NR_FRAGS, buf, total);
skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
CRYPTO_TFM_REQ_MAY_SLEEP,
crypto_req_done, &wait);
skcipher_request_set_crypt(req, sg, sg, total, iv_work);
skcipher_request_set_data_unit_size(req, du_size);
err = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);
if (err == -EOPNOTSUPP)
goto out;
if (err) {
pr_err("alg: skcipher: %s multi-DU encrypt failed (du=%u): %d\n",
driver, du_size, err);
goto out;
}
if (memcmp(buf, ref, total) != 0) {
pr_err("alg: skcipher: %s multi-DU ciphertext differs from single-DU reference (du=%u)\n",
driver, du_size);
err = -EBADMSG;
goto out;
}
/* req->iv must be unchanged after multi-DU dispatch. */
if (memcmp(iv_work, iv_orig, sizeof(iv_orig)) != 0) {
pr_err("alg: skcipher: %s multi-DU encrypt mutated caller IV (du=%u)\n",
driver, du_size);
err = -EBADMSG;
goto out;
}

/* Round-trip the batched ciphertext back to plaintext. */
test_mdu_sg_fragment(sg, TEST_MDU_NR_FRAGS, buf, total);
skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
CRYPTO_TFM_REQ_MAY_SLEEP,
crypto_req_done, &wait);
skcipher_request_set_crypt(req, sg, sg, total, iv_work);
skcipher_request_set_data_unit_size(req, du_size);
err = crypto_wait_req(crypto_skcipher_decrypt(req), &wait);
if (err) {
pr_err("alg: skcipher: %s multi-DU decrypt failed (du=%u): %d\n",
driver, du_size, err);
goto out;
}
if (memcmp(buf, plain, total) != 0) {
pr_err("alg: skcipher: %s multi-DU round-trip mismatch (du=%u)\n",
driver, du_size);
err = -EBADMSG;
}

out:
skcipher_request_free(req);
kfree(ref);
kfree(buf);
kfree(plain);
return err;
}

static int test_skcipher_multi_du(struct crypto_skcipher *tfm)
{
static const unsigned int du_sizes[] = { 512, 1024, 2048, 4096 };
unsigned int j;
int err;

if (crypto_skcipher_ivsize(tfm) != 16)
return 0;

for (j = 0; j < ARRAY_SIZE(du_sizes); j++) {
err = test_skcipher_multi_du_one(tfm, du_sizes[j]);
/* Ineligible algorithms skip; real failures propagate. */
if (err == -EOPNOTSUPP)
return 0;
if (err)
return err;
cond_resched();
}
return 0;
}

static int alg_test_skcipher(const struct alg_test_desc *desc,
const char *driver, u32 type, u32 mask)
{
Expand Down Expand Up @@ -3259,6 +3447,10 @@ static int alg_test_skcipher(const struct alg_test_desc *desc,
if (err)
goto out;

err = test_skcipher_multi_du(tfm);
if (err)
goto out;

err = test_skcipher_vs_generic_impl(desc->generic_driver, req, tsgls);
out:
free_cipher_test_sglists(tsgls);
Expand Down
Loading
Loading