Coverage Report

Created: 2024-05-21 06:33

/src/cryptsetup/lib/utils_benchmark.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * libcryptsetup - cryptsetup library, cipher benchmark
3
 *
4
 * Copyright (C) 2012-2024 Red Hat, Inc. All rights reserved.
5
 * Copyright (C) 2012-2024 Milan Broz
6
 *
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License
9
 * as published by the Free Software Foundation; either version 2
10
 * of the License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
 */
21
22
#include <stdlib.h>
23
#include <errno.h>
24
25
#include "internal.h"
26
27
int crypt_benchmark(struct crypt_device *cd,
28
  const char *cipher,
29
  const char *cipher_mode,
30
  size_t volume_key_size,
31
  size_t iv_size,
32
  size_t buffer_size,
33
  double *encryption_mbs,
34
  double *decryption_mbs)
35
0
{
36
0
  void *buffer = NULL;
37
0
  char *iv = NULL, *key = NULL, mode[MAX_CIPHER_LEN], *c;
38
0
  int r;
39
40
0
  if (!cipher || !cipher_mode || !volume_key_size || !encryption_mbs || !decryption_mbs)
41
0
    return -EINVAL;
42
43
0
  r = init_crypto(cd);
44
0
  if (r < 0)
45
0
    return r;
46
47
0
  r = -ENOMEM;
48
0
  if (posix_memalign(&buffer, crypt_getpagesize(), buffer_size))
49
0
    goto out;
50
0
  memset(buffer, 0, buffer_size);
51
52
0
  r = crypt_cipher_ivsize(cipher, cipher_mode);
53
0
  if (r >= 0 && iv_size != (size_t)r) {
54
0
    log_dbg(cd, "IV length for benchmark adjusted to %i bytes (requested %zu).", r, iv_size);
55
0
    iv_size = r;
56
0
  }
57
58
0
  if (iv_size) {
59
0
    iv = malloc(iv_size);
60
0
    if (!iv)
61
0
      goto out;
62
0
    crypt_random_get(cd, iv, iv_size, CRYPT_RND_NORMAL);
63
0
  }
64
65
0
  key = malloc(volume_key_size);
66
0
  if (!key)
67
0
    goto out;
68
69
0
  crypt_random_get(cd, key, volume_key_size, CRYPT_RND_NORMAL);
70
71
0
  strncpy(mode, cipher_mode, sizeof(mode)-1);
72
  /* Ignore IV generator */
73
0
  if ((c  = strchr(mode, '-')))
74
0
    *c = '\0';
75
76
0
  r = crypt_cipher_perf_kernel(cipher, cipher_mode, buffer, buffer_size, key, volume_key_size,
77
0
             iv, iv_size, encryption_mbs, decryption_mbs);
78
79
0
  if (r == -ERANGE)
80
0
    log_dbg(cd, "Measured cipher runtime is too low.");
81
0
  else if (r)
82
0
    log_dbg(cd, "Cannot initialize cipher %s, mode %s, key size %zu, IV size %zu.",
83
0
      cipher, cipher_mode, volume_key_size, iv_size);
84
0
out:
85
0
  free(buffer);
86
0
  free(key);
87
0
  free(iv);
88
89
0
  return r;
90
0
}
91
92
int crypt_benchmark_pbkdf(struct crypt_device *cd,
93
  struct crypt_pbkdf_type *pbkdf,
94
  const char *password,
95
  size_t password_size,
96
  const char *salt,
97
  size_t salt_size,
98
  size_t volume_key_size,
99
  int (*progress)(uint32_t time_ms, void *usrptr),
100
  void *usrptr)
101
0
{
102
0
  int r, priority;
103
0
  const char *kdf_opt;
104
0
  uint32_t memory_kb;
105
106
0
  if (!pbkdf || (!password && password_size))
107
0
    return -EINVAL;
108
109
0
  r = init_crypto(cd);
110
0
  if (r < 0)
111
0
    return r;
112
113
0
  kdf_opt = !strcmp(pbkdf->type, CRYPT_KDF_PBKDF2) ? pbkdf->hash : "";
114
115
0
  log_dbg(cd, "Running %s(%s) benchmark.", pbkdf->type, kdf_opt);
116
117
0
  memory_kb = pbkdf_adjusted_phys_memory_kb();
118
0
  if (memory_kb < pbkdf->max_memory_kb) {
119
0
    log_dbg(cd, "Not enough physical memory detected, "
120
0
      "PBKDF max memory decreased from %dkB to %dkB.",
121
0
      pbkdf->max_memory_kb, memory_kb);
122
0
    pbkdf->max_memory_kb = memory_kb;
123
0
  }
124
125
0
  crypt_process_priority(cd, &priority, true);
126
0
  r = crypt_pbkdf_perf(pbkdf->type, pbkdf->hash, password, password_size,
127
0
           salt, salt_size, volume_key_size, pbkdf->time_ms,
128
0
           pbkdf->max_memory_kb, pbkdf->parallel_threads,
129
0
           &pbkdf->iterations, &pbkdf->max_memory_kb, progress, usrptr);
130
0
  crypt_process_priority(cd, &priority, false);
131
132
0
  if (!r)
133
0
    log_dbg(cd, "Benchmark returns %s(%s) %u iterations, %u memory, %u threads (for %zu-bits key).",
134
0
      pbkdf->type, kdf_opt, pbkdf->iterations, pbkdf->max_memory_kb,
135
0
      pbkdf->parallel_threads, volume_key_size * 8);
136
0
  return r;
137
0
}
138
139
struct benchmark_usrptr {
140
  struct crypt_device *cd;
141
  struct crypt_pbkdf_type *pbkdf;
142
};
143
144
static int benchmark_callback(uint32_t time_ms, void *usrptr)
145
0
{
146
0
  struct benchmark_usrptr *u = usrptr;
147
148
0
  log_dbg(u->cd, "PBKDF benchmark: memory cost = %u, iterations = %u, "
149
0
    "threads = %u (took %u ms)", u->pbkdf->max_memory_kb,
150
0
    u->pbkdf->iterations, u->pbkdf->parallel_threads, time_ms);
151
152
0
  return 0;
153
0
}
154
155
/*
156
 * Used in internal places to benchmark crypt_device context PBKDF.
157
 * Once requested parameters are benchmarked, iterations attribute is set,
158
 * and the benchmarked values can be reused.
159
 * Note that memory cost can be changed after benchmark (if used).
160
 * NOTE: You need to check that you are benchmarking for the same key size.
161
 */
162
int crypt_benchmark_pbkdf_internal(struct crypt_device *cd,
163
           struct crypt_pbkdf_type *pbkdf,
164
           size_t volume_key_size)
165
0
{
166
0
  struct crypt_pbkdf_limits pbkdf_limits;
167
0
  double PBKDF2_tmp;
168
0
  uint32_t ms_tmp;
169
0
  int r = -EINVAL;
170
0
  struct benchmark_usrptr u = {
171
0
    .cd = cd,
172
0
    .pbkdf = pbkdf
173
0
  };
174
175
0
  r = crypt_pbkdf_get_limits(pbkdf->type, &pbkdf_limits);
176
0
  if (r)
177
0
    return r;
178
179
0
  if (pbkdf->flags & CRYPT_PBKDF_NO_BENCHMARK) {
180
0
    if (pbkdf->iterations) {
181
0
      log_dbg(cd, "Reusing PBKDF values (no benchmark flag is set).");
182
0
      return 0;
183
0
    }
184
0
    log_err(cd, _("PBKDF benchmark disabled but iterations not set."));
185
0
    return -EINVAL;
186
0
  }
187
188
  /* For PBKDF2 run benchmark always. Also note it depends on volume_key_size! */
189
0
  if (!strcmp(pbkdf->type, CRYPT_KDF_PBKDF2)) {
190
    /*
191
     * For PBKDF2 it is enough to run benchmark for only 1 second
192
     * and interpolate final iterations value from it.
193
     */
194
0
    ms_tmp = pbkdf->time_ms;
195
0
    pbkdf->time_ms = 1000;
196
0
    pbkdf->parallel_threads = 0; /* N/A in PBKDF2 */
197
0
    pbkdf->max_memory_kb = 0; /* N/A in PBKDF2 */
198
199
0
    r = crypt_benchmark_pbkdf(cd, pbkdf, "foobarfo", 8, "01234567890abcdef", 16,
200
0
          volume_key_size, &benchmark_callback, &u);
201
0
    pbkdf->time_ms = ms_tmp;
202
0
    if (r < 0) {
203
0
      log_err(cd, _("Not compatible PBKDF2 options (using hash algorithm %s)."),
204
0
        pbkdf->hash);
205
0
      return r;
206
0
    }
207
208
0
    PBKDF2_tmp = ((double)pbkdf->iterations * pbkdf->time_ms / 1000.);
209
0
    if (PBKDF2_tmp > (double)UINT32_MAX)
210
0
      return -EINVAL;
211
0
    pbkdf->iterations = AT_LEAST((uint32_t)PBKDF2_tmp, pbkdf_limits.min_iterations);
212
0
  } else {
213
    /* Already benchmarked */
214
0
    if (pbkdf->iterations) {
215
0
      log_dbg(cd, "Reusing PBKDF values.");
216
0
      return 0;
217
0
    }
218
219
0
    r = crypt_benchmark_pbkdf(cd, pbkdf, "foobarfo", 8,
220
0
      "0123456789abcdef0123456789abcdef", 32,
221
0
      volume_key_size, &benchmark_callback, &u);
222
0
    if (r < 0)
223
0
      log_err(cd, _("Not compatible PBKDF options."));
224
0
  }
225
226
0
  return r;
227
0
}