]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/cryptenroll/cryptenroll-wipe.c
hexdecoct: make unbase64mem and unhexmem always use SIZE_MAX
[thirdparty/systemd.git] / src / cryptenroll / cryptenroll-wipe.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "cryptenroll-wipe.h"
4 #include "cryptenroll.h"
5 #include "json.h"
6 #include "memory-util.h"
7 #include "parse-util.h"
8 #include "set.h"
9 #include "sort-util.h"
10
11 static int find_all_slots(struct crypt_device *cd, Set *wipe_slots, Set *keep_slots) {
12 int slot_max;
13
14 assert(cd);
15 assert(wipe_slots);
16 assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0);
17
18 /* Finds all currently assigned slots, and adds them to 'wipe_slots', except if listed already in 'keep_slots' */
19
20 for (int slot = 0; slot < slot_max; slot++) {
21 crypt_keyslot_info status;
22
23 /* No need to check this slot if we already know we want to wipe it or definitely keep it. */
24 if (set_contains(keep_slots, INT_TO_PTR(slot)) ||
25 set_contains(wipe_slots, INT_TO_PTR(slot)))
26 continue;
27
28 status = crypt_keyslot_status(cd, slot);
29 if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST))
30 continue;
31
32 if (set_put(wipe_slots, INT_TO_PTR(slot)) < 0)
33 return log_oom();
34 }
35
36 return 0;
37 }
38
39 static int find_empty_passphrase_slots(struct crypt_device *cd, Set *wipe_slots, Set *keep_slots) {
40 size_t vks;
41 int r, slot_max;
42
43 assert(cd);
44 assert(wipe_slots);
45 assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0);
46
47 /* Finds all slots with an empty passphrase assigned (i.e. "") and adds them to 'wipe_slots', except
48 * if listed already in 'keep_slots' */
49
50 r = crypt_get_volume_key_size(cd);
51 if (r <= 0)
52 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size");
53 vks = (size_t) r;
54
55 for (int slot = 0; slot < slot_max; slot++) {
56 _cleanup_(erase_and_freep) char *vk = NULL;
57 crypt_keyslot_info status;
58
59 /* No need to check this slot if we already know we want to wipe it or definitely keep it. */
60 if (set_contains(keep_slots, INT_TO_PTR(slot)) ||
61 set_contains(wipe_slots, INT_TO_PTR(slot)))
62 continue;
63
64 status = crypt_keyslot_status(cd, slot);
65 if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST))
66 continue;
67
68 vk = malloc(vks);
69 if (!vk)
70 return log_oom();
71
72 r = crypt_volume_key_get(cd, slot, vk, &vks, "", 0);
73 if (r < 0) {
74 log_debug_errno(r, "Failed to acquire volume key from slot %i with empty password, ignoring: %m", slot);
75 continue;
76 }
77
78 if (set_put(wipe_slots, INT_TO_PTR(r)) < 0)
79 return log_oom();
80 }
81
82 return 0;
83 }
84
85 static int find_slots_by_mask(
86 struct crypt_device *cd,
87 Set *wipe_slots,
88 Set *keep_slots,
89 unsigned by_mask) {
90
91 _cleanup_set_free_ Set *listed_slots = NULL;
92 int r;
93
94 assert(cd);
95 assert(wipe_slots);
96
97 if (by_mask == 0)
98 return 0;
99
100 /* Find all slots that are associated with a token of a type in the specified token type mask */
101
102 for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
103 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
104 JsonVariant *w, *z;
105 EnrollType t;
106
107 r = cryptsetup_get_token_as_json(cd, token, NULL, &v);
108 if (IN_SET(r, -ENOENT, -EINVAL))
109 continue;
110 if (r < 0) {
111 log_warning_errno(r, "Failed to read JSON token data off disk, ignoring: %m");
112 continue;
113 }
114
115 w = json_variant_by_key(v, "type");
116 if (!w || !json_variant_is_string(w)) {
117 log_warning("Token JSON data lacks type field, ignoring.");
118 continue;
119 }
120
121 t = luks2_token_type_from_string(json_variant_string(w));
122
123 w = json_variant_by_key(v, "keyslots");
124 if (!w || !json_variant_is_array(w)) {
125 log_warning("Token JSON data lacks keyslots field, ignoring.");
126 continue;
127 }
128
129 JSON_VARIANT_ARRAY_FOREACH(z, w) {
130 int slot;
131
132 if (!json_variant_is_string(z)) {
133 log_warning("Token JSON data's keyslot field is not an array of strings, ignoring.");
134 continue;
135 }
136
137 r = safe_atoi(json_variant_string(z), &slot);
138 if (r < 0) {
139 log_warning_errno(r, "Token JSON data's keyslot filed is not an integer formatted as string, ignoring.");
140 continue;
141 }
142
143 if (t >= 0 && (by_mask & (1U << t)) != 0) {
144 /* Selected by token type */
145 if (set_put(wipe_slots, INT_TO_PTR(slot)) < 0)
146 return log_oom();
147 } else if ((by_mask & (1U << ENROLL_PASSWORD)) != 0) {
148 /* If we shall remove all plain password slots, let's maintain a list of
149 * slots that are listed in any tokens, since those are *NOT* plain
150 * passwords */
151 if (set_ensure_allocated(&listed_slots, NULL) < 0)
152 return log_oom();
153
154 if (set_put(listed_slots, INT_TO_PTR(slot)) < 0)
155 return log_oom();
156 }
157 }
158 }
159
160 /* "password" slots are those which have no token assigned. If we shall remove those, iterate through
161 * all slots and mark those for wiping that weren't listed in any token */
162 if ((by_mask & (1U << ENROLL_PASSWORD)) != 0) {
163 int slot_max;
164
165 assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0);
166
167 for (int slot = 0; slot < slot_max; slot++) {
168 crypt_keyslot_info status;
169
170 /* No need to check this slot if we already know we want to wipe it or definitely keep it. */
171 if (set_contains(keep_slots, INT_TO_PTR(slot)) ||
172 set_contains(wipe_slots, INT_TO_PTR(slot)))
173 continue;
174
175 if (set_contains(listed_slots, INT_TO_PTR(slot))) /* This has a token, hence is not a password. */
176 continue;
177
178 status = crypt_keyslot_status(cd, slot);
179 if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST)) /* Not actually assigned? */
180 continue;
181
182 /* Finally, we found a password, add it to the list of slots to wipe */
183 if (set_put(wipe_slots, INT_TO_PTR(slot)) < 0)
184 return log_oom();
185 }
186 }
187
188 return 0;
189 }
190
191 static int find_slot_tokens(struct crypt_device *cd, Set *wipe_slots, Set *keep_slots, Set *wipe_tokens) {
192 int r;
193
194 assert(cd);
195 assert(wipe_slots);
196 assert(keep_slots);
197 assert(wipe_tokens);
198
199 /* Find all tokens matching the slots we want to wipe, so that we can wipe them too. Also, for update
200 * the slots sets according to the token data: add any other slots listed in the tokens we act on. */
201
202 for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
203 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
204 bool shall_wipe = false;
205 JsonVariant *w, *z;
206
207 r = cryptsetup_get_token_as_json(cd, token, NULL, &v);
208 if (IN_SET(r, -ENOENT, -EINVAL))
209 continue;
210 if (r < 0) {
211 log_warning_errno(r, "Failed to read JSON token data off disk, ignoring: %m");
212 continue;
213 }
214
215 w = json_variant_by_key(v, "keyslots");
216 if (!w || !json_variant_is_array(w)) {
217 log_warning("Token JSON data lacks keyslots field, ignoring.");
218 continue;
219 }
220
221 /* Go through the slots associated with this token: if we shall keep any slot of them, the token shall stay too. */
222 JSON_VARIANT_ARRAY_FOREACH(z, w) {
223 int slot;
224
225 if (!json_variant_is_string(z)) {
226 log_warning("Token JSON data's keyslot field is not an array of strings, ignoring.");
227 continue;
228 }
229
230 r = safe_atoi(json_variant_string(z), &slot);
231 if (r < 0) {
232 log_warning_errno(r, "Token JSON data's keyslot filed is not an integer formatted as string, ignoring.");
233 continue;
234 }
235
236 if (set_contains(keep_slots, INT_TO_PTR(slot))) {
237 shall_wipe = false;
238 break; /* If we shall keep this slot, then this is definite: we will keep its token too */
239 }
240
241 /* If there's a slot associated with this token that we shall wipe, then remove the
242 * token too. But we are careful here: let's continue iterating, maybe there's a slot
243 * that we need to keep, in which case we can reverse the decision again. */
244 if (set_contains(wipe_slots, INT_TO_PTR(slot)))
245 shall_wipe = true;
246 }
247
248 /* Go through the slots again, and this time add them to the list of slots to keep/remove */
249 JSON_VARIANT_ARRAY_FOREACH(z, w) {
250 int slot;
251
252 if (!json_variant_is_string(z))
253 continue;
254 if (safe_atoi(json_variant_string(z), &slot) < 0)
255 continue;
256
257 if (set_put(shall_wipe ? wipe_slots : keep_slots, INT_TO_PTR(slot)) < 0)
258 return log_oom();
259 }
260
261 /* And of course, also remember the tokens to remove. */
262 if (shall_wipe)
263 if (set_put(wipe_tokens, INT_TO_PTR(token)) < 0)
264 return log_oom();
265 }
266
267 return 0;
268 }
269
270 static bool slots_remain(struct crypt_device *cd, Set *wipe_slots, Set *keep_slots) {
271 int slot_max;
272
273 assert(cd);
274 assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0);
275
276 /* Checks if any slots remaining in the LUKS2 header if we remove all slots listed in 'wipe_slots'
277 * (keeping those listed in 'keep_slots') */
278
279 for (int slot = 0; slot < slot_max; slot++) {
280 crypt_keyslot_info status;
281
282 status = crypt_keyslot_status(cd, slot);
283 if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST))
284 continue;
285
286 /* The "keep" set wins if a slot is listed in both sets. This is important so that we can
287 * safely add a new slot and remove all others of the same type, which in a naive
288 * implementation might mean we remove what we just added — which we of course don't want. */
289 if (set_contains(keep_slots, INT_TO_PTR(slot)) ||
290 !set_contains(wipe_slots, INT_TO_PTR(slot)))
291 return true;
292 }
293
294 return false;
295 }
296
297 int wipe_slots(struct crypt_device *cd,
298 const int explicit_slots[],
299 size_t n_explicit_slots,
300 WipeScope by_scope,
301 unsigned by_mask,
302 int except_slot) {
303
304 _cleanup_set_free_ Set *wipe_slots = NULL, *wipe_tokens = NULL, *keep_slots = NULL;
305 _cleanup_free_ int *ordered_slots = NULL, *ordered_tokens = NULL;
306 size_t n_ordered_slots = 0, n_ordered_tokens = 0;
307 int r, slot_max, ret;
308 void *e;
309
310 assert_se(cd);
311
312 /* Shortcut if nothing to wipe. */
313 if (n_explicit_slots == 0 && by_mask == 0 && by_scope == WIPE_EXPLICIT)
314 return 0;
315
316 /* So this is a bit more complicated than I'd wish, but we want support three different axis for wiping slots:
317 *
318 * 1. Wiping by slot indexes
319 * 2. Wiping slots of specified token types
320 * 3. Wiping "all" entries, or entries with an empty password (i.e. "")
321 *
322 * (or any combination of the above)
323 *
324 * Plus: We always want to remove tokens matching the slots.
325 * Plus: We always want to exclude the slots/tokens we just added.
326 */
327
328 wipe_slots = set_new(NULL);
329 keep_slots = set_new(NULL);
330 wipe_tokens = set_new(NULL);
331 if (!wipe_slots || !keep_slots || !wipe_tokens)
332 return log_oom();
333
334 /* Let's maintain one set of slots for the slots we definitely want to keep */
335 if (except_slot >= 0)
336 if (set_put(keep_slots, INT_TO_PTR(except_slot)) < 0)
337 return log_oom();
338
339 assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0);
340
341 /* Maintain another set of the slots we intend to wipe */
342 for (size_t i = 0; i < n_explicit_slots; i++) {
343 if (explicit_slots[i] >= slot_max)
344 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Slot index %i out of range.", explicit_slots[i]);
345
346 if (set_put(wipe_slots, INT_TO_PTR(explicit_slots[i])) < 0)
347 return log_oom();
348 }
349
350 /* Now, handle the "all" and "empty passphrase" cases. */
351 switch (by_scope) {
352
353 case WIPE_EXPLICIT:
354 break; /* Nothing to do here */
355
356 case WIPE_ALL:
357 r = find_all_slots(cd, wipe_slots, keep_slots);
358 if (r < 0)
359 return r;
360
361 break;
362
363 case WIPE_EMPTY_PASSPHRASE:
364 r = find_empty_passphrase_slots(cd, wipe_slots, keep_slots);
365 if (r < 0)
366 return r;
367
368 break;
369 default:
370 assert_not_reached();
371 }
372
373 /* Then add all slots that match a token type */
374 r = find_slots_by_mask(cd, wipe_slots, keep_slots, by_mask);
375 if (r < 0)
376 return r;
377
378 /* And determine tokens that we shall remove */
379 r = find_slot_tokens(cd, wipe_slots, keep_slots, wipe_tokens);
380 if (r < 0)
381 return r;
382
383 /* Safety check: let's make sure that after we are done there's at least one slot remaining */
384 if (!slots_remain(cd, wipe_slots, keep_slots))
385 return log_error_errno(SYNTHETIC_ERRNO(EPERM),
386 "Wipe operation would leave no valid slots around, can't allow that, sorry.");
387
388 /* Generated ordered lists of the slots and the tokens to remove */
389 ordered_slots = new(int, set_size(wipe_slots));
390 if (!ordered_slots)
391 return log_oom();
392 SET_FOREACH(e, wipe_slots) {
393 int slot = PTR_TO_INT(e);
394
395 if (set_contains(keep_slots, INT_TO_PTR(slot)))
396 continue;
397
398 ordered_slots[n_ordered_slots++] = slot;
399 }
400 typesafe_qsort(ordered_slots, n_ordered_slots, cmp_int);
401
402 ordered_tokens = new(int, set_size(wipe_tokens));
403 if (!ordered_tokens)
404 return log_oom();
405 SET_FOREACH(e, wipe_tokens)
406 ordered_tokens[n_ordered_tokens++] = PTR_TO_INT(e);
407 typesafe_qsort(ordered_tokens, n_ordered_tokens, cmp_int);
408
409 if (n_ordered_slots == 0 && n_ordered_tokens == 0) {
410 log_full(except_slot < 0 ? LOG_NOTICE : LOG_DEBUG,
411 "No slots to remove selected.");
412 return 0;
413 }
414
415 if (DEBUG_LOGGING) {
416 for (size_t i = 0; i < n_ordered_slots; i++)
417 log_debug("Going to wipe slot %i.", ordered_slots[i]);
418 for (size_t i = 0; i < n_ordered_tokens; i++)
419 log_debug("Going to wipe token %i.", ordered_tokens[i]);
420 }
421
422 /* Now, let's actually start wiping things. (We go from back to front, to make space at the end
423 * first.) */
424 ret = 0;
425 for (size_t i = n_ordered_slots; i > 0; i--) {
426 r = crypt_keyslot_destroy(cd, ordered_slots[i - 1]);
427 if (r < 0) {
428 log_warning_errno(r, "Failed to wipe slot %i, continuing: %m", ordered_slots[i - 1]);
429 if (ret == 0)
430 ret = r;
431 } else
432 log_info("Wiped slot %i.", ordered_slots[i - 1]);
433 }
434
435 for (size_t i = n_ordered_tokens; i > 0; i--) {
436 r = crypt_token_json_set(cd, ordered_tokens[i - 1], NULL);
437 if (r < 0) {
438 log_warning_errno(r, "Failed to wipe token %i, continuing: %m", ordered_tokens[i - 1]);
439 if (ret == 0)
440 ret = r;
441 }
442 }
443
444 return ret;
445 }