Fixed the build error for riscv64 arch using gcc 13
[platform/upstream/cryptsetup.git] / lib / utils_benchmark.c
index 4ee0c03..6b2efdc 100644 (file)
@@ -1,12 +1,13 @@
 /*
- * libcryptsetup - cryptsetup library, cipher bechmark
+ * libcryptsetup - cryptsetup library, cipher benchmark
  *
- * Copyright (C) 2012, Red Hat, Inc. All rights reserved.
- * Copyright (C) 2012, Milan Broz
+ * Copyright (C) 2012-2021 Red Hat, Inc. All rights reserved.
+ * Copyright (C) 2012-2021 Milan Broz
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
- * version 2 as published by the Free Software Foundation.
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 #include <stdlib.h>
 #include <errno.h>
-#include <sys/time.h>
-#include <sys/resource.h>
 
 #include "internal.h"
 
-/*
- * This is not simulating storage, so using disk block causes extreme overhead.
- * Let's use some fixed block size where results are more reliable...
- */
-#define CIPHER_BLOCK_BYTES 65536
-
-/*
- * The whole test depends on Linux kernel usermode crypto API for now.
- * (The same implementations are used in dm-crypt though.)
- */
-
-struct cipher_perf {
-       char name[32];
-       char mode[32];
-       char *key;
-       size_t key_length;
-       char *iv;
-       size_t iv_length;
-       size_t buffer_size;
-};
-
-static long time_ms(struct rusage *start, struct rusage *end)
-{
-       long ms = 0;
-
-       /* For kernel backend, we need to measure only tim in kernel.
-       ms = (end->ru_utime.tv_sec - start->ru_utime.tv_sec) * 1000;
-       ms += (end->ru_utime.tv_usec - start->ru_utime.tv_usec) / 1000;
-       */
-
-       ms += (end->ru_stime.tv_sec - start->ru_stime.tv_sec) * 1000;
-       ms += (end->ru_stime.tv_usec - start->ru_stime.tv_usec) / 1000;
-
-       return ms;
-}
-
-static int cipher_perf_one(struct cipher_perf *cp, char *buf,
-                          size_t buf_size, int enc)
-{
-       struct crypt_cipher *cipher = NULL;
-       size_t done = 0, block = CIPHER_BLOCK_BYTES;
-       int r;
-
-       if (buf_size < block)
-               block = buf_size;
-
-       r = crypt_cipher_init(&cipher, cp->name, cp->mode, cp->key, cp->key_length);
-       if (r < 0) {
-               log_dbg("Cannot initialise cipher %s, mode %s.", cp->name, cp->mode);
-               return r;
-       }
-
-       while (done < buf_size) {
-               if ((done + block) > buf_size)
-                       block = buf_size - done;
-
-               if (enc)
-                       r = crypt_cipher_encrypt(cipher, &buf[done], &buf[done],
-                                                block, cp->iv, cp->iv_length);
-               else
-                       r = crypt_cipher_decrypt(cipher, &buf[done], &buf[done],
-                                                block, cp->iv, cp->iv_length);
-               if (r < 0)
-                       break;
-
-               done += block;
-       }
-
-       crypt_cipher_destroy(cipher);
-
-       return r;
-}
-static long cipher_measure(struct cipher_perf *cp, char *buf,
-                          size_t buf_size, int encrypt)
-{
-       struct rusage rstart, rend;
-       int r;
-
-       if (getrusage(RUSAGE_SELF, &rstart) < 0)
-               return -EINVAL;
-
-       r = cipher_perf_one(cp, buf, buf_size, encrypt);
-       if (r < 0)
-               return r;
-
-       if (getrusage(RUSAGE_SELF, &rend) < 0)
-               return -EINVAL;
-
-       return time_ms(&rstart, &rend);
-}
-
-static double speed_mbs(unsigned long bytes, unsigned long ms)
-{
-       double speed = bytes, s = ms / 1000.;
-
-       return speed / (1024 * 1024) / s;
-}
-
-static int cipher_perf(struct cipher_perf *cp,
-       double *encryption_mbs, double *decryption_mbs)
-{
-       long ms_enc, ms_dec, ms;
-       int repeat_enc, repeat_dec;
-       void *buf = NULL;
-
-       if (posix_memalign(&buf, crypt_getpagesize(), cp->buffer_size))
-               return -ENOMEM;
-
-       ms_enc = 0;
-       repeat_enc = 1;
-       while (ms_enc < 1000) {
-               ms = cipher_measure(cp, buf, cp->buffer_size, 1);
-               if (ms < 0) {
-                       free(buf);
-                       return (int)ms;
-               }
-               ms_enc += ms;
-               repeat_enc++;
-       }
-
-       ms_dec = 0;
-       repeat_dec = 1;
-       while (ms_dec < 1000) {
-               ms = cipher_measure(cp, buf, cp->buffer_size, 0);
-               if (ms < 0) {
-                       free(buf);
-                       return (int)ms;
-               }
-               ms_dec += ms;
-               repeat_dec++;
-       }
-
-       free(buf);
-
-       *encryption_mbs = speed_mbs(cp->buffer_size * repeat_enc, ms_enc);
-       *decryption_mbs = speed_mbs(cp->buffer_size * repeat_dec, ms_dec);
-
-       return  0;
-}
-
 int crypt_benchmark(struct crypt_device *cd,
        const char *cipher,
        const char *cipher_mode,
@@ -174,15 +33,11 @@ int crypt_benchmark(struct crypt_device *cd,
        double *encryption_mbs,
        double *decryption_mbs)
 {
-       struct cipher_perf cp = {
-               .key_length = volume_key_size,
-               .iv_length = iv_size,
-               .buffer_size = buffer_size,
-       };
-       char *c;
+       void *buffer = NULL;
+       char *iv = NULL, *key = NULL, mode[MAX_CIPHER_LEN], *c;
        int r;
 
-       if (!cipher || !cipher_mode || !volume_key_size)
+       if (!cipher || !cipher_mode || !volume_key_size || !encryption_mbs || !decryption_mbs)
                return -EINVAL;
 
        r = init_crypto(cd);
@@ -190,58 +45,171 @@ int crypt_benchmark(struct crypt_device *cd,
                return r;
 
        r = -ENOMEM;
+       if (posix_memalign(&buffer, crypt_getpagesize(), buffer_size))
+               goto out;
+
+       r = crypt_cipher_ivsize(cipher, cipher_mode);
+       if (r >= 0 && iv_size != (size_t)r) {
+               log_dbg(cd, "IV length for benchmark adjusted to %i bytes (requested %zu).", r, iv_size);
+               iv_size = r;
+       }
+
        if (iv_size) {
-               cp.iv = malloc(iv_size);
-               if (!cp.iv)
+               iv = malloc(iv_size);
+               if (!iv)
                        goto out;
-               crypt_random_get(cd, cp.iv, iv_size, CRYPT_RND_NORMAL);
+               crypt_random_get(cd, iv, iv_size, CRYPT_RND_NORMAL);
        }
 
-       cp.key = malloc(volume_key_size);
-       if (!cp.key)
+       key = malloc(volume_key_size);
+       if (!key)
                goto out;
 
-       crypt_random_get(cd, cp.key, volume_key_size, CRYPT_RND_NORMAL);
-       strncpy(cp.name, cipher, sizeof(cp.name)-1);
-       strncpy(cp.mode, cipher_mode, sizeof(cp.mode)-1);
+       crypt_random_get(cd, key, volume_key_size, CRYPT_RND_NORMAL);
 
+       strncpy(mode, cipher_mode, sizeof(mode)-1);
        /* Ignore IV generator */
-       if ((c  = strchr(cp.mode, '-')))
+       if ((c  = strchr(mode, '-')))
                *c = '\0';
 
-       r = cipher_perf(&cp, encryption_mbs, decryption_mbs);
+       r = crypt_cipher_perf_kernel(cipher, cipher_mode, buffer, buffer_size, key, volume_key_size,
+                                    iv, iv_size, encryption_mbs, decryption_mbs);
+
+       if (r == -ERANGE)
+               log_dbg(cd, "Measured cipher runtime is too low.");
+       else if (r)
+               log_dbg(cd, "Cannot initialize cipher %s, mode %s, key size %zu, IV size %zu.",
+                       cipher, cipher_mode, volume_key_size, iv_size);
 out:
-       free(cp.key);
-       free(cp.iv);
+       free(buffer);
+       free(key);
+       free(iv);
+
        return r;
 }
 
-int crypt_benchmark_kdf(struct crypt_device *cd,
-       const char *kdf,
-       const char *hash,
+int crypt_benchmark_pbkdf(struct crypt_device *cd,
+       struct crypt_pbkdf_type *pbkdf,
        const char *password,
        size_t password_size,
        const char *salt,
        size_t salt_size,
-       uint64_t *iterations_sec)
+       size_t volume_key_size,
+       int (*progress)(uint32_t time_ms, void *usrptr),
+       void *usrptr)
 {
        int r;
+       const char *kdf_opt;
 
-       if (!iterations_sec)
+       if (!pbkdf || (!password && password_size))
                return -EINVAL;
 
        r = init_crypto(cd);
        if (r < 0)
                return r;
 
-       if (!strncmp(kdf, "pbkdf2", 6))
-               r = crypt_pbkdf_check(kdf, hash, password, password_size,
-                                     salt, salt_size, iterations_sec);
-       else
-               r = -EINVAL;
+       kdf_opt = !strcmp(pbkdf->type, CRYPT_KDF_PBKDF2) ? pbkdf->hash : "";
+
+       log_dbg(cd, "Running %s(%s) benchmark.", pbkdf->type, kdf_opt);
+
+       r = crypt_pbkdf_perf(pbkdf->type, pbkdf->hash, password, password_size,
+                            salt, salt_size, volume_key_size, pbkdf->time_ms,
+                            pbkdf->max_memory_kb, pbkdf->parallel_threads,
+                            &pbkdf->iterations, &pbkdf->max_memory_kb, progress, usrptr);
 
        if (!r)
-               log_dbg("KDF %s, hash %s: %" PRIu64 " iterations per second.",
-                       kdf, hash, *iterations_sec);
+               log_dbg(cd, "Benchmark returns %s(%s) %u iterations, %u memory, %u threads (for %zu-bits key).",
+                       pbkdf->type, kdf_opt, pbkdf->iterations, pbkdf->max_memory_kb,
+                       pbkdf->parallel_threads, volume_key_size * 8);
+       return r;
+}
+
+struct benchmark_usrptr {
+       struct crypt_device *cd;
+       struct crypt_pbkdf_type *pbkdf;
+};
+
+static int benchmark_callback(uint32_t time_ms, void *usrptr)
+{
+       struct benchmark_usrptr *u = usrptr;
+
+       log_dbg(u->cd, "PBKDF benchmark: memory cost = %u, iterations = %u, "
+               "threads = %u (took %u ms)", u->pbkdf->max_memory_kb,
+               u->pbkdf->iterations, u->pbkdf->parallel_threads, time_ms);
+
+       return 0;
+}
+
+/*
+ * Used in internal places to benchmark crypt_device context PBKDF.
+ * Once requested parameters are benchmarked, iterations attribute is set,
+ * and the benchmarked values can be reused.
+ * Note that memory cost can be changed after benchmark (if used).
+ * NOTE: You need to check that you are benchmarking for the same key size.
+ */
+int crypt_benchmark_pbkdf_internal(struct crypt_device *cd,
+                                  struct crypt_pbkdf_type *pbkdf,
+                                  size_t volume_key_size)
+{
+       struct crypt_pbkdf_limits pbkdf_limits;
+       double PBKDF2_tmp;
+       uint32_t ms_tmp;
+       int r = -EINVAL;
+       struct benchmark_usrptr u = {
+               .cd = cd,
+               .pbkdf = pbkdf
+       };
+
+       r = crypt_pbkdf_get_limits(pbkdf->type, &pbkdf_limits);
+       if (r)
+               return r;
+
+       if (pbkdf->flags & CRYPT_PBKDF_NO_BENCHMARK) {
+               if (pbkdf->iterations) {
+                       log_dbg(cd, "Reusing PBKDF values (no benchmark flag is set).");
+                       return 0;
+               }
+               log_err(cd, _("PBKDF benchmark disabled but iterations not set."));
+               return -EINVAL;
+       }
+
+       /* For PBKDF2 run benchmark always. Also note it depends on volume_key_size! */
+       if (!strcmp(pbkdf->type, CRYPT_KDF_PBKDF2)) {
+               /*
+                * For PBKDF2 it is enough to run benchmark for only 1 second
+                * and interpolate final iterations value from it.
+                */
+               ms_tmp = pbkdf->time_ms;
+               pbkdf->time_ms = 1000;
+               pbkdf->parallel_threads = 0; /* N/A in PBKDF2 */
+               pbkdf->max_memory_kb = 0; /* N/A in PBKDF2 */
+
+               r = crypt_benchmark_pbkdf(cd, pbkdf, "foo", 3, "bar", 3,
+                                       volume_key_size, &benchmark_callback, &u);
+               pbkdf->time_ms = ms_tmp;
+               if (r < 0) {
+                       log_err(cd, _("Not compatible PBKDF2 options (using hash algorithm %s)."),
+                               pbkdf->hash);
+                       return r;
+               }
+
+               PBKDF2_tmp = ((double)pbkdf->iterations * pbkdf->time_ms / 1000.);
+               if (PBKDF2_tmp > (double)UINT32_MAX)
+                       return -EINVAL;
+               pbkdf->iterations = at_least((uint32_t)PBKDF2_tmp, pbkdf_limits.min_iterations);
+       } else {
+               /* Already benchmarked */
+               if (pbkdf->iterations) {
+                       log_dbg(cd, "Reusing PBKDF values.");
+                       return 0;
+               }
+
+               r = crypt_benchmark_pbkdf(cd, pbkdf, "foo", 3,
+                       "0123456789abcdef0123456789abcdef", 32,
+                       volume_key_size, &benchmark_callback, &u);
+               if (r < 0)
+                       log_err(cd, _("Not compatible PBKDF options."));
+       }
+
        return r;
 }