Coverage Report

Created: 2026-03-03 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cryptsetup/lib/crypto_backend/pbkdf_check.c
Line
Count
Source
1
// SPDX-License-Identifier: LGPL-2.1-or-later
2
/*
3
 * PBKDF performance check
4
 * Copyright (C) 2012-2025 Red Hat, Inc. All rights reserved.
5
 * Copyright (C) 2012-2025 Milan Broz
6
 * Copyright (C) 2016-2020 Ondrej Mosnacek
7
 */
8
9
#include <stdlib.h>
10
#include <errno.h>
11
#include <limits.h>
12
#include <time.h>
13
#include <sys/time.h>
14
#include <sys/resource.h>
15
#include "crypto_backend.h"
16
17
#ifndef CLOCK_MONOTONIC_RAW
18
#define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC
19
#endif
20
21
0
#define BENCH_MIN_MS 250
22
0
#define BENCH_MIN_MS_FAST 10
23
0
#define BENCH_PERCENT_ATLEAST 95
24
0
#define BENCH_PERCENT_ATMOST 110
25
0
#define BENCH_SAMPLES_FAST 3
26
0
#define BENCH_SAMPLES_SLOW 1
27
28
/* These PBKDF2 limits must be never violated */
29
int crypt_pbkdf_get_limits(const char *kdf, struct crypt_pbkdf_limits *limits)
30
6.56k
{
31
6.56k
  if (!kdf || !limits)
32
0
    return -EINVAL;
33
34
6.56k
  if (!strcmp(kdf, "pbkdf2")) {
35
2.90k
    limits->min_iterations = 1000; /* recommendation in NIST SP 800-132 */
36
2.90k
    limits->max_iterations = UINT32_MAX;
37
2.90k
    limits->min_memory     = 0; /* N/A */
38
2.90k
    limits->min_bench_memory=0; /* N/A */
39
2.90k
    limits->max_memory     = 0; /* N/A */
40
2.90k
    limits->min_parallel   = 0; /* N/A */
41
2.90k
    limits->max_parallel   = 0; /* N/A */
42
2.90k
    return 0;
43
3.66k
  } else if (!strcmp(kdf, "argon2i") || !strcmp(kdf, "argon2id")) {
44
3.66k
    limits->min_iterations = 4;
45
3.66k
    limits->max_iterations = UINT32_MAX;
46
3.66k
    limits->min_memory     = 32;      /* hard limit */
47
3.66k
    limits->min_bench_memory=64*1024; /* 64 MiB minimum for benchmark */
48
3.66k
    limits->max_memory     = 4*1024*1024; /* 4GiB */
49
3.66k
    limits->min_parallel   = 1;
50
3.66k
    limits->max_parallel   = 4;
51
3.66k
    return 0;
52
3.66k
  }
53
54
0
  return -EINVAL;
55
6.56k
}
56
57
static long time_ms(struct rusage *start, struct rusage *end)
58
0
{
59
0
  int count_kernel_time = 0;
60
0
  long ms;
61
62
0
  if (crypt_backend_flags() & CRYPT_BACKEND_KERNEL)
63
0
    count_kernel_time = 1;
64
65
  /*
66
   * If there is no self usage info, count system time.
67
   * This seem like getrusage() bug in some hypervisors...
68
   */
69
0
  if (!end->ru_utime.tv_sec && !start->ru_utime.tv_sec &&
70
0
      !end->ru_utime.tv_usec && !start->ru_utime.tv_usec)
71
0
    count_kernel_time = 1;
72
73
0
  ms = (end->ru_utime.tv_sec - start->ru_utime.tv_sec) * 1000;
74
0
  ms += (end->ru_utime.tv_usec - start->ru_utime.tv_usec) / 1000;
75
76
0
  if (count_kernel_time) {
77
0
    ms += (end->ru_stime.tv_sec - start->ru_stime.tv_sec) * 1000;
78
0
    ms += (end->ru_stime.tv_usec - start->ru_stime.tv_usec) / 1000;
79
0
  }
80
81
0
  return ms;
82
0
}
83
84
static long timespec_ms(struct timespec *start, struct timespec *end)
85
0
{
86
0
  return (end->tv_sec - start->tv_sec) * 1000 +
87
0
          (end->tv_nsec - start->tv_nsec) / (1000 * 1000);
88
0
}
89
90
static int measure_argon2(const char *kdf, const char *password, size_t password_length,
91
        const char *salt, size_t salt_length,
92
        char *key, size_t key_length,
93
        uint32_t t_cost, uint32_t m_cost, uint32_t parallel,
94
        size_t samples, long ms_atleast, long *out_ms)
95
0
{
96
0
  long ms, ms_min = LONG_MAX;
97
0
  int r;
98
0
  size_t i;
99
100
0
  for (i = 0; i < samples; i++) {
101
0
    struct timespec tstart, tend;
102
103
    /*
104
     * NOTE: We must use clock_gettime here, because Argon2 can run over
105
     * multiple threads, and thus we care about real time, not CPU time!
106
     */
107
0
    if (clock_gettime(CLOCK_MONOTONIC_RAW, &tstart) < 0)
108
0
      return -EINVAL;
109
110
0
    r = crypt_pbkdf(kdf, NULL, password, password_length, salt,
111
0
                    salt_length, key, key_length, t_cost, m_cost, parallel);
112
0
    if (r < 0)
113
0
      return r;
114
115
0
    if (clock_gettime(CLOCK_MONOTONIC_RAW, &tend) < 0)
116
0
      return -EINVAL;
117
118
0
    ms = timespec_ms(&tstart, &tend);
119
0
    if (ms < 0)
120
0
      return -EINVAL;
121
122
0
    if (ms < ms_atleast) {
123
      /* early exit */
124
0
      ms_min = ms;
125
0
      break;
126
0
    }
127
0
    if (ms < ms_min) {
128
0
      ms_min = ms;
129
0
    }
130
0
  }
131
0
  *out_ms = ms_min;
132
0
  return 0;
133
0
}
134
135
0
#define CONTINUE 0
136
0
#define FINAL   1
137
static int next_argon2_params(uint32_t *t_cost, uint32_t *m_cost,
138
            uint32_t min_t_cost, uint32_t min_m_cost,
139
            uint32_t max_m_cost, long ms, uint32_t target_ms)
140
0
{
141
0
  uint32_t old_t_cost, old_m_cost, new_t_cost, new_m_cost;
142
0
  uint64_t num, denom;
143
144
0
  old_t_cost = *t_cost;
145
0
  old_m_cost = *m_cost;
146
147
0
  if ((uint32_t)ms > target_ms) {
148
    /* decreasing, first try to lower t_cost, then m_cost */
149
0
    num = (uint64_t)*t_cost * (uint64_t)target_ms;
150
0
    denom = (uint64_t)ms;
151
0
    new_t_cost = (uint32_t)(num / denom);
152
0
    if (new_t_cost < min_t_cost) {
153
0
      num = (uint64_t)*t_cost * (uint64_t)*m_cost *
154
0
            (uint64_t)target_ms;
155
0
      denom = (uint64_t)min_t_cost * (uint64_t)ms;
156
0
      *t_cost = min_t_cost;
157
0
      *m_cost = (uint32_t)(num / denom);
158
0
      if (*m_cost < min_m_cost) {
159
0
        *m_cost = min_m_cost;
160
0
        return FINAL;
161
0
      }
162
0
    } else {
163
0
      *t_cost = new_t_cost;
164
0
    }
165
0
  } else {
166
    /* increasing, first try to increase m_cost, then t_cost */
167
0
    num = (uint64_t)*m_cost * (uint64_t)target_ms;
168
0
    denom = (uint64_t)ms;
169
0
    new_m_cost = (uint32_t)(num / denom);
170
0
    if (new_m_cost > max_m_cost) {
171
0
      num = (uint64_t)*t_cost * (uint64_t)*m_cost *
172
0
            (uint64_t)target_ms;
173
0
      denom = (uint64_t)max_m_cost * (uint64_t)ms;
174
0
      *t_cost = (uint32_t)(num / denom);
175
0
      *m_cost = max_m_cost;
176
0
      if (*t_cost <= min_t_cost) {
177
0
        *t_cost = min_t_cost;
178
0
        return FINAL;
179
0
      }
180
0
    } else if (new_m_cost < min_m_cost) {
181
0
      *m_cost = min_m_cost;
182
0
      return FINAL;
183
0
    } else {
184
0
      *m_cost = new_m_cost;
185
0
    }
186
0
  }
187
188
  /* do not continue if it is the same as in the previous run */
189
0
  if (old_t_cost == *t_cost && old_m_cost == *m_cost)
190
0
    return FINAL;
191
192
0
  return CONTINUE;
193
0
}
194
195
static int crypt_argon2_check(const char *kdf, const char *password,
196
            size_t password_length, const char *salt,
197
            size_t salt_length, size_t key_length,
198
            uint32_t min_t_cost, uint32_t min_m_cost, uint32_t max_m_cost,
199
            uint32_t parallel, uint32_t target_ms,
200
            uint32_t *out_t_cost, uint32_t *out_m_cost,
201
            int (*progress)(uint32_t time_ms, void *usrptr),
202
            void *usrptr)
203
0
{
204
0
  int r = 0;
205
0
  char *key = NULL;
206
0
  uint32_t t_cost, m_cost;
207
0
  long ms;
208
0
  long ms_atleast = (long)target_ms * BENCH_PERCENT_ATLEAST / 100;
209
0
  long ms_atmost = (long)target_ms * BENCH_PERCENT_ATMOST / 100;
210
211
0
  if (key_length <= 0 || target_ms <= 0)
212
0
    return -EINVAL;
213
214
0
  if (min_m_cost < (parallel * 8))
215
0
    min_m_cost = parallel * 8;
216
217
0
  if (max_m_cost < min_m_cost)
218
0
    return -EINVAL;
219
220
0
  key = malloc(key_length);
221
0
  if (!key)
222
0
    return -ENOMEM;
223
224
0
  t_cost = min_t_cost;
225
0
  m_cost = min_m_cost;
226
227
  /* 1. Find some small parameters, s. t. ms >= BENCH_MIN_MS: */
228
0
  while (1) {
229
0
    r = measure_argon2(kdf, password, password_length, salt, salt_length,
230
0
                       key, key_length, t_cost, m_cost, parallel,
231
0
                       BENCH_SAMPLES_FAST, BENCH_MIN_MS, &ms);
232
0
    if (!r) {
233
      /* Update parameters to actual measurement */
234
0
      *out_t_cost = t_cost;
235
0
      *out_m_cost = m_cost;
236
0
      if (progress && progress((uint32_t)ms, usrptr))
237
0
        r = -EINTR;
238
0
    }
239
240
0
    if (r < 0)
241
0
      goto out;
242
243
0
    if (ms >= BENCH_MIN_MS)
244
0
      break;
245
246
0
    if (m_cost == max_m_cost) {
247
0
      if (ms < BENCH_MIN_MS_FAST)
248
0
        t_cost *= 16;
249
0
      else {
250
0
        uint32_t new = (t_cost * BENCH_MIN_MS) / (uint32_t)ms;
251
0
        if (new == t_cost)
252
0
          break;
253
254
0
        t_cost = new;
255
0
      }
256
0
    } else {
257
0
      if (ms < BENCH_MIN_MS_FAST)
258
0
        m_cost *= 16;
259
0
      else {
260
0
        uint32_t new = (m_cost * BENCH_MIN_MS) / (uint32_t)ms;
261
0
        if (new == m_cost)
262
0
          break;
263
264
0
        m_cost = new;
265
0
      }
266
0
      if (m_cost > max_m_cost) {
267
0
        m_cost = max_m_cost;
268
0
      }
269
0
    }
270
0
  }
271
  /*
272
   * 2. Use the params obtained in (1.) to estimate the target params.
273
   * 3. Then repeatedly measure the candidate params and if they fall out of
274
   * the acceptance range (+-5 %), try to improve the estimate:
275
   */
276
0
  do {
277
0
    if (next_argon2_params(&t_cost, &m_cost, min_t_cost, min_m_cost,
278
0
               max_m_cost, ms, target_ms)) {
279
      /* Update parameters to final computation */
280
0
      *out_t_cost = t_cost;
281
0
      *out_m_cost = m_cost;
282
0
      break;
283
0
    }
284
285
0
    r = measure_argon2(kdf, password, password_length, salt, salt_length,
286
0
                       key, key_length, t_cost, m_cost, parallel,
287
0
                       BENCH_SAMPLES_SLOW, ms_atleast, &ms);
288
289
0
    if (!r) {
290
      /* Update parameters to actual measurement */
291
0
      *out_t_cost = t_cost;
292
0
      *out_m_cost = m_cost;
293
0
      if (progress && progress((uint32_t)ms, usrptr))
294
0
        r = -EINTR;
295
0
    }
296
297
0
    if (r < 0)
298
0
      break;
299
300
0
  } while (ms < ms_atleast || ms > ms_atmost);
301
0
out:
302
0
  if (key) {
303
    /* Key can be derived from a real provided password */
304
0
    crypt_backend_memzero(key, key_length);
305
0
    free(key);
306
0
  }
307
0
  return r;
308
0
}
309
310
/* This code benchmarks PBKDF and returns iterations/second using specified hash */
311
static int crypt_pbkdf_check(const char *kdf, const char *hash,
312
          const char *password, size_t password_length,
313
          const char *salt, size_t salt_length,
314
          size_t key_length, uint32_t *iter_secs, uint32_t target_ms,
315
          int (*progress)(uint32_t time_ms, void *usrptr), void *usrptr)
316
317
0
{
318
0
  struct rusage rstart, rend;
319
0
  int r = 0, step = 0;
320
0
  long ms = 0;
321
0
  char *key = NULL;
322
0
  uint32_t iterations;
323
0
  double PBKDF2_temp;
324
325
0
  if (!kdf || !hash || key_length <= 0)
326
0
    return -EINVAL;
327
328
0
  key = malloc(key_length);
329
0
  if (!key)
330
0
    return -ENOMEM;
331
332
0
  *iter_secs = 0;
333
0
  iterations = 1 << 15;
334
0
  while (1) {
335
0
    if (getrusage(RUSAGE_SELF, &rstart) < 0) {
336
0
      r = -EINVAL;
337
0
      goto out;
338
0
    }
339
340
0
    r = crypt_pbkdf(kdf, hash, password, password_length, salt,
341
0
        salt_length, key, key_length, iterations, 0, 0);
342
343
0
    if (r < 0)
344
0
      goto out;
345
346
0
    if (getrusage(RUSAGE_SELF, &rend) < 0) {
347
0
      r = -EINVAL;
348
0
      goto out;
349
0
    }
350
351
0
    ms = time_ms(&rstart, &rend);
352
0
    if (ms) {
353
0
      PBKDF2_temp = (double)iterations * target_ms / ms;
354
0
      if (PBKDF2_temp > UINT32_MAX) {
355
0
        r = -EINVAL;
356
0
        goto out;
357
0
      }
358
0
      *iter_secs = (uint32_t)PBKDF2_temp;
359
0
    }
360
361
0
    if (progress && progress((uint32_t)ms, usrptr)) {
362
0
      r = -EINTR;
363
0
      goto out;
364
0
    }
365
366
0
    if (ms > 500)
367
0
      break;
368
369
0
    if (ms <= 62)
370
0
      iterations <<= 4;
371
0
    else if (ms <= 125)
372
0
      iterations <<= 3;
373
0
    else if (ms <= 250)
374
0
      iterations <<= 2;
375
0
    else
376
0
      iterations <<= 1;
377
378
0
    if (++step > 10 || !iterations) {
379
0
      r = -EINVAL;
380
0
      goto out;
381
0
    }
382
0
  }
383
0
out:
384
0
  if (key) {
385
    /* Key can be derived from a real provided password */
386
0
    crypt_backend_memzero(key, key_length);
387
0
    free(key);
388
0
  }
389
0
  return r;
390
0
}
391
392
int crypt_pbkdf_perf(const char *kdf, const char *hash,
393
    const char *password, size_t password_size,
394
    const char *salt, size_t salt_size,
395
    size_t volume_key_size, uint32_t time_ms,
396
    uint32_t max_memory_kb, uint32_t parallel_threads,
397
    uint32_t *iterations_out, uint32_t *memory_out,
398
    int (*progress)(uint32_t time_ms, void *usrptr), void *usrptr)
399
0
{
400
0
  struct crypt_pbkdf_limits pbkdf_limits;
401
0
  int r = -EINVAL;
402
0
  uint32_t min_memory;
403
404
0
  if (!kdf || !iterations_out || !memory_out)
405
0
    return -EINVAL;
406
407
0
  r = crypt_pbkdf_get_limits(kdf, &pbkdf_limits);
408
0
  if (r < 0)
409
0
    return r;
410
411
0
  if (parallel_threads > pbkdf_limits.max_parallel)
412
0
    return -EINVAL;
413
414
0
  min_memory = pbkdf_limits.min_bench_memory;
415
0
  if (min_memory > max_memory_kb)
416
0
    min_memory = max_memory_kb;
417
418
0
  *memory_out = 0;
419
0
  *iterations_out = 0;
420
421
0
  if (!strcmp(kdf, "pbkdf2"))
422
0
    r = crypt_pbkdf_check(kdf, hash, password, password_size,
423
0
              salt, salt_size, volume_key_size,
424
0
              iterations_out, time_ms, progress, usrptr);
425
426
0
  else if (!strncmp(kdf, "argon2", 6))
427
0
    r = crypt_argon2_check(kdf, password, password_size,
428
0
               salt, salt_size, volume_key_size,
429
0
               pbkdf_limits.min_iterations,
430
0
               min_memory,
431
0
               max_memory_kb,
432
0
               parallel_threads, time_ms, iterations_out,
433
0
               memory_out, progress, usrptr);
434
0
  return r;
435
0
}