Merge branch 'upstream' into tizen
[platform/upstream/cryptsetup.git] / lib / utils_benchmark.c
1 /*
2  * libcryptsetup - cryptsetup library, cipher benchmark
3  *
4  * Copyright (C) 2012-2023 Red Hat, Inc. All rights reserved.
5  * Copyright (C) 2012-2023 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 {
36         void *buffer = NULL;
37         char *iv = NULL, *key = NULL, mode[MAX_CIPHER_LEN], *c;
38         int r;
39
40         if (!cipher || !cipher_mode || !volume_key_size || !encryption_mbs || !decryption_mbs)
41                 return -EINVAL;
42
43         r = init_crypto(cd);
44         if (r < 0)
45                 return r;
46
47         r = -ENOMEM;
48         if (posix_memalign(&buffer, crypt_getpagesize(), buffer_size))
49                 goto out;
50         memset(buffer, 0, buffer_size);
51
52         r = crypt_cipher_ivsize(cipher, cipher_mode);
53         if (r >= 0 && iv_size != (size_t)r) {
54                 log_dbg(cd, "IV length for benchmark adjusted to %i bytes (requested %zu).", r, iv_size);
55                 iv_size = r;
56         }
57
58         if (iv_size) {
59                 iv = malloc(iv_size);
60                 if (!iv)
61                         goto out;
62                 crypt_random_get(cd, iv, iv_size, CRYPT_RND_NORMAL);
63         }
64
65         key = malloc(volume_key_size);
66         if (!key)
67                 goto out;
68
69         crypt_random_get(cd, key, volume_key_size, CRYPT_RND_NORMAL);
70
71         strncpy(mode, cipher_mode, sizeof(mode)-1);
72         /* Ignore IV generator */
73         if ((c  = strchr(mode, '-')))
74                 *c = '\0';
75
76         r = crypt_cipher_perf_kernel(cipher, cipher_mode, buffer, buffer_size, key, volume_key_size,
77                                      iv, iv_size, encryption_mbs, decryption_mbs);
78
79         if (r == -ERANGE)
80                 log_dbg(cd, "Measured cipher runtime is too low.");
81         else if (r)
82                 log_dbg(cd, "Cannot initialize cipher %s, mode %s, key size %zu, IV size %zu.",
83                         cipher, cipher_mode, volume_key_size, iv_size);
84 out:
85         free(buffer);
86         free(key);
87         free(iv);
88
89         return r;
90 }
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 {
102         int r, priority;
103         const char *kdf_opt;
104
105         if (!pbkdf || (!password && password_size))
106                 return -EINVAL;
107
108         r = init_crypto(cd);
109         if (r < 0)
110                 return r;
111
112         kdf_opt = !strcmp(pbkdf->type, CRYPT_KDF_PBKDF2) ? pbkdf->hash : "";
113
114         log_dbg(cd, "Running %s(%s) benchmark.", pbkdf->type, kdf_opt);
115
116         crypt_process_priority(cd, &priority, true);
117         r = crypt_pbkdf_perf(pbkdf->type, pbkdf->hash, password, password_size,
118                              salt, salt_size, volume_key_size, pbkdf->time_ms,
119                              pbkdf->max_memory_kb, pbkdf->parallel_threads,
120                              &pbkdf->iterations, &pbkdf->max_memory_kb, progress, usrptr);
121         crypt_process_priority(cd, &priority, false);
122
123         if (!r)
124                 log_dbg(cd, "Benchmark returns %s(%s) %u iterations, %u memory, %u threads (for %zu-bits key).",
125                         pbkdf->type, kdf_opt, pbkdf->iterations, pbkdf->max_memory_kb,
126                         pbkdf->parallel_threads, volume_key_size * 8);
127         return r;
128 }
129
130 struct benchmark_usrptr {
131         struct crypt_device *cd;
132         struct crypt_pbkdf_type *pbkdf;
133 };
134
135 static int benchmark_callback(uint32_t time_ms, void *usrptr)
136 {
137         struct benchmark_usrptr *u = usrptr;
138
139         log_dbg(u->cd, "PBKDF benchmark: memory cost = %u, iterations = %u, "
140                 "threads = %u (took %u ms)", u->pbkdf->max_memory_kb,
141                 u->pbkdf->iterations, u->pbkdf->parallel_threads, time_ms);
142
143         return 0;
144 }
145
146 /*
147  * Used in internal places to benchmark crypt_device context PBKDF.
148  * Once requested parameters are benchmarked, iterations attribute is set,
149  * and the benchmarked values can be reused.
150  * Note that memory cost can be changed after benchmark (if used).
151  * NOTE: You need to check that you are benchmarking for the same key size.
152  */
153 int crypt_benchmark_pbkdf_internal(struct crypt_device *cd,
154                                    struct crypt_pbkdf_type *pbkdf,
155                                    size_t volume_key_size)
156 {
157         struct crypt_pbkdf_limits pbkdf_limits;
158         double PBKDF2_tmp;
159         uint32_t ms_tmp;
160         int r = -EINVAL;
161         struct benchmark_usrptr u = {
162                 .cd = cd,
163                 .pbkdf = pbkdf
164         };
165
166         r = crypt_pbkdf_get_limits(pbkdf->type, &pbkdf_limits);
167         if (r)
168                 return r;
169
170         if (pbkdf->flags & CRYPT_PBKDF_NO_BENCHMARK) {
171                 if (pbkdf->iterations) {
172                         log_dbg(cd, "Reusing PBKDF values (no benchmark flag is set).");
173                         return 0;
174                 }
175                 log_err(cd, _("PBKDF benchmark disabled but iterations not set."));
176                 return -EINVAL;
177         }
178
179         /* For PBKDF2 run benchmark always. Also note it depends on volume_key_size! */
180         if (!strcmp(pbkdf->type, CRYPT_KDF_PBKDF2)) {
181                 /*
182                  * For PBKDF2 it is enough to run benchmark for only 1 second
183                  * and interpolate final iterations value from it.
184                  */
185                 ms_tmp = pbkdf->time_ms;
186                 pbkdf->time_ms = 1000;
187                 pbkdf->parallel_threads = 0; /* N/A in PBKDF2 */
188                 pbkdf->max_memory_kb = 0; /* N/A in PBKDF2 */
189
190                 r = crypt_benchmark_pbkdf(cd, pbkdf, "foobarfo", 8, "01234567890abcdef", 16,
191                                         volume_key_size, &benchmark_callback, &u);
192                 pbkdf->time_ms = ms_tmp;
193                 if (r < 0) {
194                         log_err(cd, _("Not compatible PBKDF2 options (using hash algorithm %s)."),
195                                 pbkdf->hash);
196                         return r;
197                 }
198
199                 PBKDF2_tmp = ((double)pbkdf->iterations * pbkdf->time_ms / 1000.);
200                 if (PBKDF2_tmp > (double)UINT32_MAX)
201                         return -EINVAL;
202                 pbkdf->iterations = AT_LEAST((uint32_t)PBKDF2_tmp, pbkdf_limits.min_iterations);
203         } else {
204                 /* Already benchmarked */
205                 if (pbkdf->iterations) {
206                         log_dbg(cd, "Reusing PBKDF values.");
207                         return 0;
208                 }
209
210                 r = crypt_benchmark_pbkdf(cd, pbkdf, "foobarfo", 8,
211                         "0123456789abcdef0123456789abcdef", 32,
212                         volume_key_size, &benchmark_callback, &u);
213                 if (r < 0)
214                         log_err(cd, _("Not compatible PBKDF options."));
215         }
216
217         return r;
218 }