/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 | } |