Coverage Report

Created: 2026-08-30 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cryptsetup/lib/integrity/integrity.c
Line
Count
Source
1
// SPDX-License-Identifier: LGPL-2.1-or-later
2
/*
3
 * Integrity volume handling
4
 *
5
 * Copyright (C) 2016-2026 Milan Broz
6
 */
7
8
#include <errno.h>
9
#include <stdio.h>
10
#include <stdlib.h>
11
#include <string.h>
12
#include <uuid/uuid.h>
13
14
#include "integrity.h"
15
#include "internal.h"
16
17
/* For LUKS2, integrity metadata are on DATA device even for detached header! */
18
static struct device *INTEGRITY_metadata_device(struct crypt_device *cd)
19
0
{
20
0
  const char *type = crypt_get_type(cd);
21
22
0
  if (type && !strcmp(type, CRYPT_LUKS2))
23
0
    return crypt_data_device(cd);
24
25
0
  return crypt_metadata_device(cd);
26
0
}
27
28
static int INTEGRITY_read_superblock(struct crypt_device *cd,
29
             struct device *device,
30
             uint64_t offset, struct superblock *sb)
31
0
{
32
0
  int devfd, r;
33
34
0
  log_dbg(cd, "Reading kernel dm-integrity metadata on %s.", device_path(device));
35
36
0
  devfd = device_open(cd, device, O_RDONLY);
37
0
  if(devfd < 0)
38
0
    return -EINVAL;
39
40
0
  if (read_lseek_blockwise(devfd, device_block_size(cd, device),
41
0
      device_alignment(device), sb, sizeof(*sb), offset) != sizeof(*sb)) {
42
0
    log_dbg(cd, "Cannot read kernel dm-integrity metadata on %s.", device_path(device));
43
0
    return -EINVAL;
44
0
  }
45
46
0
  if (memcmp(sb->magic, SB_MAGIC, sizeof(sb->magic))) {
47
0
    log_dbg(cd, "No kernel dm-integrity metadata detected on %s.", device_path(device));
48
0
    r = -EINVAL;
49
0
  } else if (sb->version < SB_VERSION_1 || sb->version > SB_VERSION_7) {
50
0
    log_err(cd, _("Incompatible kernel dm-integrity metadata (version %u) detected on %s."),
51
0
      sb->version, device_path(device));
52
0
    r = -EINVAL;
53
0
  } else {
54
0
    if ((uint32_t)sb->log2_sectors_per_block + SECTOR_SHIFT >= 32) {
55
0
      log_dbg(cd, "dm-integrity log2_sectors_per_block overflow");
56
0
      return -EINVAL;
57
0
    }
58
0
    sb->integrity_tag_size = le16toh(sb->integrity_tag_size);
59
0
    sb->journal_sections = le32toh(sb->journal_sections);
60
0
    sb->provided_data_sectors = le64toh(sb->provided_data_sectors);
61
0
    sb->recalc_sector = le64toh(sb->recalc_sector);
62
0
    sb->flags = le32toh(sb->flags);
63
0
    r = 0;
64
0
  }
65
66
0
  return r;
67
0
}
68
69
int INTEGRITY_read_sb(struct crypt_device *cd,
70
          struct crypt_params_integrity *params,
71
          uint32_t *flags)
72
0
{
73
0
  struct superblock sb;
74
0
  int r;
75
76
0
  r = INTEGRITY_read_superblock(cd, INTEGRITY_metadata_device(cd), 0, &sb);
77
0
  if (r)
78
0
    return r;
79
80
0
  if (params) {
81
0
    params->sector_size = (uint32_t)SECTOR_SIZE << sb.log2_sectors_per_block;
82
0
    params->tag_size = sb.integrity_tag_size;
83
0
  }
84
85
0
  if (flags)
86
0
    *flags = sb.flags;
87
88
0
  return 0;
89
0
}
90
91
int INTEGRITY_dump(struct crypt_device *cd, struct device *device, uint64_t offset)
92
0
{
93
0
  struct superblock sb;
94
0
  uint64_t sector_size;
95
0
  int r;
96
97
0
  r = INTEGRITY_read_superblock(cd, device, offset, &sb);
98
0
  if (r)
99
0
    return r;
100
101
0
  sector_size = (uint64_t)SECTOR_SIZE << sb.log2_sectors_per_block;
102
0
  log_std(cd, "INTEGRITY header information for %s.\n", device_path(device));
103
0
  log_std(cd, "version: %d\n", (unsigned)sb.version);
104
0
  log_std(cd, "tag size: %u [bytes]\n", sb.integrity_tag_size);
105
0
  log_std(cd, "sector size: %" PRIu64 " [bytes]\n", sector_size);
106
0
  log_std(cd, "data size: %" PRIu64 " [512-byte units] (%" PRIu64 " [bytes])\n",
107
0
    sb.provided_data_sectors, sb.provided_data_sectors * SECTOR_SIZE);
108
0
  if (sb.version >= SB_VERSION_2 && (sb.flags & SB_FLAG_RECALCULATING))
109
0
    log_std(cd, "recalculate sector: %" PRIu64 "\n", sb.recalc_sector);
110
0
  log_std(cd, "journal sections: %u\n", sb.journal_sections);
111
0
  log_std(cd, "log2 interleave sectors: %d\n", sb.log2_interleave_sectors);
112
0
  log_std(cd, "log2 blocks per bitmap: %u\n", sb.log2_blocks_per_bitmap_bit);
113
0
  log_std(cd, "flags: %s%s%s%s%s%s%s\n",
114
0
    sb.flags & SB_FLAG_HAVE_JOURNAL_MAC ? "have_journal_mac " : "",
115
0
    sb.flags & SB_FLAG_RECALCULATING ? "recalculating " : "",
116
0
    sb.flags & SB_FLAG_DIRTY_BITMAP ? "dirty_bitmap " : "",
117
0
    sb.flags & SB_FLAG_FIXED_PADDING ? "fix_padding " : "",
118
0
    sb.flags & SB_FLAG_FIXED_HMAC ? "fix_hmac " : "",
119
0
    sb.flags & SB_FLAG_INLINE ? "inline " : "",
120
0
    sb.flags & SB_FLAG_DISCARD_KEYED ? "discards_keyed " : "");
121
122
0
  return 0;
123
0
}
124
125
int INTEGRITY_data_sectors(struct crypt_device *cd,
126
         struct device *device, uint64_t offset,
127
         uint64_t *data_sectors)
128
0
{
129
0
  struct superblock sb;
130
0
  int r;
131
132
0
  r = INTEGRITY_read_superblock(cd, device, offset, &sb);
133
0
  if (r)
134
0
    return r;
135
136
0
  *data_sectors = sb.provided_data_sectors;
137
0
  return 0;
138
0
}
139
140
int INTEGRITY_key_size(const char *integrity, int required_key_size)
141
0
{
142
0
  int ks = 0;
143
144
0
  if (!integrity && required_key_size)
145
0
    return -EINVAL;
146
147
0
  if (!integrity)
148
0
    return 0;
149
150
  //FIXME: use crypto backend hash size
151
0
  if (!strcmp(integrity, "aead"))
152
0
    ks = 0;
153
0
  else if (!strcmp(integrity, "hmac(sha1)"))
154
0
    ks = required_key_size ?: 20;
155
0
  else if (!strcmp(integrity, "hmac(sha256)"))
156
0
    ks = required_key_size ?: 32;
157
0
  else if (!strcmp(integrity, "hmac(sha512)"))
158
0
    ks = required_key_size ?: 64;
159
0
  else if (!strcmp(integrity, "phmac(sha1)"))
160
0
    ks = required_key_size ?: -EINVAL;
161
0
  else if (!strcmp(integrity, "phmac(sha256)"))
162
0
    ks = required_key_size ?: -EINVAL;
163
0
  else if (!strcmp(integrity, "phmac(sha512)"))
164
0
    ks = required_key_size ?: -EINVAL;
165
0
  else if (!strcmp(integrity, "poly1305"))
166
0
    ks = 0;
167
0
  else if (!strcmp(integrity, "none"))
168
0
    ks = 0;
169
0
  else
170
0
    return -EINVAL;
171
172
0
  if (required_key_size && ks != required_key_size)
173
0
    return -EINVAL;
174
175
0
  return ks;
176
0
}
177
178
/* Return hash or hmac(hash) size, if known */
179
int INTEGRITY_hash_tag_size(const char *integrity)
180
0
{
181
0
  char hash[MAX_CIPHER_LEN];
182
0
  int r;
183
184
0
  if (!integrity)
185
0
    return 0;
186
187
0
  if (!strcmp(integrity, "crc32") || !strcmp(integrity, "crc32c"))
188
0
    return 4;
189
190
0
  if (!strcmp(integrity, "xxhash64"))
191
0
    return 8;
192
193
0
  r = sscanf(integrity, "hmac(%" MAX_CIPHER_LEN_STR "[^)]s", hash);
194
0
  if (r != 1)
195
0
    r = sscanf(integrity, "phmac(%" MAX_CIPHER_LEN_STR "[^)]s", hash);
196
0
  if (r == 1)
197
0
    r = crypt_hash_size(hash);
198
0
  else
199
0
    r = crypt_hash_size(integrity);
200
201
0
  return r < 0 ? 0 : r;
202
0
}
203
204
int INTEGRITY_tag_size(const char *integrity,
205
           const char *cipher,
206
           const char *cipher_mode)
207
0
{
208
0
  int iv_tag_size = 0, auth_tag_size = 0;
209
210
0
  if (!cipher_mode)
211
0
    iv_tag_size = 0;
212
0
  else if (!strcmp(cipher_mode, "xts-random"))
213
0
    iv_tag_size = 16;
214
0
  else if (!strcmp(cipher_mode, "gcm-random"))
215
0
    iv_tag_size = 12;
216
0
  else if (!strcmp(cipher_mode, "ccm-random"))
217
0
    iv_tag_size = 8;
218
0
  else if (!strcmp(cipher_mode, "ctr-random"))
219
0
    iv_tag_size = 16;
220
0
  else if (!strcmp(cipher, "aegis256") && !strcmp(cipher_mode, "random"))
221
0
    iv_tag_size = 32;
222
0
  else if (!strcmp(cipher_mode, "random"))
223
0
    iv_tag_size = 16;
224
225
  //FIXME: use crypto backend hash size
226
0
  if (!integrity || !strcmp(integrity, "none"))
227
0
    auth_tag_size = 0;
228
0
  else if (!strcmp(integrity, "aead"))
229
0
    auth_tag_size = 16; /* gcm- mode only */
230
0
  else if (!strcmp(integrity, "cmac(aes)"))
231
0
    auth_tag_size = 16;
232
0
  else if (!strcmp(integrity, "hmac(sha1)"))
233
0
    auth_tag_size = 20;
234
0
  else if (!strcmp(integrity, "hmac(sha256)"))
235
0
    auth_tag_size = 32;
236
0
  else if (!strcmp(integrity, "hmac(sha512)"))
237
0
    auth_tag_size = 64;
238
0
  else if (!strcmp(integrity, "phmac(sha1)"))
239
0
    auth_tag_size = 20;
240
0
  else if (!strcmp(integrity, "phmac(sha256)"))
241
0
    auth_tag_size = 32;
242
0
  else if (!strcmp(integrity, "phmac(sha512)"))
243
0
    auth_tag_size = 64;
244
0
  else if (!strcmp(integrity, "poly1305")) {
245
0
    if (iv_tag_size)
246
0
      iv_tag_size = 12;
247
0
    auth_tag_size = 16;
248
0
  }
249
250
0
  return iv_tag_size + auth_tag_size;
251
0
}
252
253
int INTEGRITY_create_dmd_device(struct crypt_device *cd,
254
           const struct crypt_params_integrity *params,
255
           struct volume_key *vk,
256
           struct volume_key *journal_crypt_key,
257
           struct volume_key *journal_mac_key,
258
           struct crypt_dm_active_device *dmd,
259
           uint32_t flags, uint32_t sb_flags)
260
0
{
261
0
  int r;
262
263
0
  if (!dmd)
264
0
    return -EINVAL;
265
266
0
  *dmd = (struct crypt_dm_active_device) {
267
0
    .flags = flags,
268
0
  };
269
270
  /* Workaround for kernel dm-integrity table bug */
271
0
  if (sb_flags & SB_FLAG_RECALCULATING)
272
0
    dmd->flags |= CRYPT_ACTIVATE_RECALCULATE;
273
274
0
  if (sb_flags & SB_FLAG_INLINE)
275
0
    dmd->flags |= (CRYPT_ACTIVATE_NO_JOURNAL | CRYPT_ACTIVATE_INLINE_MODE);
276
277
0
  r = INTEGRITY_data_sectors(cd, INTEGRITY_metadata_device(cd),
278
0
           crypt_get_data_offset(cd) * SECTOR_SIZE, &dmd->size);
279
0
  if (r < 0)
280
0
    return r;
281
282
0
  return dm_integrity_target_set(cd, &dmd->segment, 0, dmd->size,
283
0
      INTEGRITY_metadata_device(cd), crypt_data_device(cd),
284
0
      crypt_get_integrity_tag_size(cd), crypt_get_data_offset(cd),
285
0
      crypt_get_sector_size(cd), vk, journal_crypt_key,
286
0
      journal_mac_key, params);
287
0
}
288
289
int INTEGRITY_activate_dmd_device(struct crypt_device *cd,
290
           const char *name,
291
           const char *type,
292
           struct crypt_dm_active_device *dmd,
293
           uint32_t sb_flags)
294
0
{
295
0
  int r;
296
0
  uint64_t dmi_flags;
297
0
  struct dm_target *tgt = &dmd->segment;
298
299
0
  if (!single_segment(dmd) || tgt->type != DM_INTEGRITY)
300
0
    return -EINVAL;
301
302
0
  log_dbg(cd, "Trying to activate INTEGRITY device on top of %s, using name %s, tag size %d%s, provided sectors %" PRIu64".",
303
0
    device_path(tgt->data_device), name, tgt->u.integrity.tag_size,
304
0
    (sb_flags & SB_FLAG_INLINE) ? " (inline)" :"", dmd->size);
305
306
0
  r = create_or_reload_device(cd, name, type, dmd);
307
308
0
  if (r < 0 && (dm_flags(cd, DM_INTEGRITY, &dmi_flags) || !(dmi_flags & DM_INTEGRITY_SUPPORTED))) {
309
0
    log_err(cd, _("Kernel does not support dm-integrity mapping."));
310
0
    return -ENOTSUP;
311
0
  }
312
313
0
  if (r < 0 && (sb_flags & SB_FLAG_FIXED_PADDING) && !dm_flags(cd, DM_INTEGRITY, &dmi_flags) &&
314
0
      !(dmi_flags & DM_INTEGRITY_FIX_PADDING_SUPPORTED)) {
315
0
    log_err(cd, _("Kernel does not support dm-integrity fixed metadata alignment."));
316
0
    return -ENOTSUP;
317
0
  }
318
319
0
  if (r < 0 && (dmd->flags & CRYPT_ACTIVATE_RECALCULATE) &&
320
0
      !(crypt_get_compatibility(cd) & CRYPT_COMPAT_LEGACY_INTEGRITY_RECALC) &&
321
0
      ((sb_flags & SB_FLAG_FIXED_HMAC) ?
322
0
      (tgt->u.integrity.vk && !tgt->u.integrity.journal_integrity_key) :
323
0
      (tgt->u.integrity.vk || tgt->u.integrity.journal_integrity_key))) {
324
0
    log_err(cd, _("Kernel refuses to activate insecure recalculate option (see legacy activation options to override)."));
325
0
    return -ENOTSUP;
326
0
  }
327
328
0
  if (r < 0 && (sb_flags & SB_FLAG_INLINE) && !dm_flags(cd, DM_INTEGRITY, &dmi_flags) &&
329
0
      !(dmi_flags & DM_INTEGRITY_INLINE_MODE_SUPPORTED)) {
330
0
    log_err(cd, _("Kernel does not support dm-integrity inline mode."));
331
0
    return -ENOTSUP;
332
0
  }
333
334
0
  return r;
335
0
}
336
337
int INTEGRITY_activate(struct crypt_device *cd,
338
           const char *name,
339
           const struct crypt_params_integrity *params,
340
           struct volume_key *vk,
341
           struct volume_key *journal_crypt_key,
342
           struct volume_key *journal_mac_key,
343
           uint32_t flags, uint32_t sb_flags)
344
0
{
345
0
  struct crypt_dm_active_device dmdq = {}, dmd = {};
346
0
  int r;
347
348
0
  if (flags & CRYPT_ACTIVATE_REFRESH) {
349
0
    r = dm_query_device(cd, name, DM_ACTIVE_CRYPT_KEYSIZE |
350
0
                DM_ACTIVE_CRYPT_KEY |
351
0
                DM_ACTIVE_INTEGRITY_PARAMS |
352
0
                DM_ACTIVE_JOURNAL_CRYPT_KEY |
353
0
                DM_ACTIVE_JOURNAL_MAC_KEY, &dmdq);
354
0
    if (r < 0)
355
0
      return r;
356
357
0
    r = INTEGRITY_create_dmd_device(cd, params, vk ?: dmdq.segment.u.integrity.vk,
358
0
            journal_crypt_key ?: dmdq.segment.u.integrity.journal_crypt_key,
359
0
            journal_mac_key ?: dmdq.segment.u.integrity.journal_integrity_key,
360
0
            &dmd, flags, sb_flags);
361
362
0
    if (!r)
363
0
      dmd.size = dmdq.size;
364
0
  } else
365
0
    r = INTEGRITY_create_dmd_device(cd, params, vk, journal_crypt_key,
366
0
            journal_mac_key, &dmd, flags, sb_flags);
367
368
0
  if (!r)
369
0
    r = INTEGRITY_activate_dmd_device(cd, name, CRYPT_INTEGRITY, &dmd, sb_flags);
370
371
0
  dm_targets_free(cd, &dmdq);
372
0
  dm_targets_free(cd, &dmd);
373
0
  return r;
374
0
}
375
376
static int _create_reduced_device(struct crypt_device *cd,
377
          const char *name,
378
          uint64_t device_size_sectors,
379
          struct device **ret_device)
380
0
{
381
0
  int r;
382
0
  char path[PATH_MAX];
383
0
  struct device *dev;
384
385
0
  struct crypt_dm_active_device dmd = {
386
0
    .size = device_size_sectors,
387
0
    .flags = CRYPT_ACTIVATE_PRIVATE,
388
0
  };
389
390
0
  assert(cd);
391
0
  assert(name);
392
0
  assert(device_size_sectors);
393
0
  assert(ret_device);
394
395
0
  r = snprintf(path, sizeof(path), "%s/%s", dm_get_dir(), name);
396
0
  if (r < 0 || (size_t)r >= sizeof(path))
397
0
    return -EINVAL;
398
399
0
  r = device_block_adjust(cd, crypt_data_device(cd), DEV_OK,
400
0
        crypt_get_data_offset(cd), &device_size_sectors, &dmd.flags);
401
0
  if (r)
402
0
    return r;
403
404
0
  log_dbg(cd, "Activating reduced helper device %s.", name);
405
406
0
  r = dm_linear_target_set(&dmd.segment, 0, dmd.size, crypt_data_device(cd), crypt_get_data_offset(cd));
407
0
  if (!r)
408
0
    r = dm_create_device(cd, name, CRYPT_SUBDEV, &dmd);
409
0
  dm_targets_free(cd, &dmd);
410
0
  if (r < 0)
411
0
    return r;
412
413
0
  r = device_alloc(cd, &dev, path);
414
0
  if (!r) {
415
0
    *ret_device = dev;
416
0
    return 0;
417
0
  }
418
419
0
  dm_remove_device(cd, name, CRYPT_DEACTIVATE_FORCE);
420
421
0
  return r;
422
0
}
423
424
int INTEGRITY_format(struct crypt_device *cd,
425
         const struct crypt_params_integrity *params,
426
         struct volume_key *integrity_key,
427
         struct volume_key *journal_crypt_key,
428
         struct volume_key *journal_mac_key,
429
         uint64_t backing_device_sectors,
430
         uint32_t *sb_flags,
431
         bool integrity_inline)
432
0
{
433
0
  uint64_t dmi_flags;
434
0
  char reduced_device_name[70], tmp_name[64], tmp_uuid[40];
435
0
  struct crypt_dm_active_device dmdi = {
436
0
    .size = 8,
437
0
    .flags = CRYPT_ACTIVATE_PRIVATE, /* We always create journal but it can be unused later */
438
0
  };
439
0
  struct dm_target *tgt = &dmdi.segment;
440
0
  int r;
441
0
  uuid_t tmp_uuid_bin;
442
0
  uint64_t data_offset_sectors;
443
0
  struct device *p_metadata_device, *p_data_device, *reduced_device = NULL;
444
445
0
  uuid_generate(tmp_uuid_bin);
446
0
  uuid_unparse(tmp_uuid_bin, tmp_uuid);
447
448
0
  r = snprintf(tmp_name, sizeof(tmp_name), "temporary-cryptsetup-%s", tmp_uuid);
449
0
  if (r < 0 || (size_t)r >= sizeof(tmp_name))
450
0
    return -EINVAL;
451
452
0
  p_metadata_device = INTEGRITY_metadata_device(cd);
453
454
0
  if (backing_device_sectors) {
455
0
    r = snprintf(reduced_device_name, sizeof(reduced_device_name),
456
0
           "temporary-cryptsetup-reduced-%s", tmp_uuid);
457
0
    if (r < 0 || (size_t)r >= sizeof(reduced_device_name))
458
0
      return -EINVAL;
459
460
    /*
461
     * Creates reduced dm-linear mapping over data device starting at
462
     * crypt_data_offset(cd) and backing_device_sectors in size.
463
     */
464
0
    r = _create_reduced_device(cd, reduced_device_name,
465
0
             backing_device_sectors, &reduced_device);
466
0
    if (r < 0)
467
0
      return r;
468
469
0
    data_offset_sectors = 0;
470
0
    p_data_device = reduced_device;
471
0
    if (p_metadata_device == crypt_data_device(cd))
472
0
      p_metadata_device = reduced_device;
473
0
  } else {
474
0
    data_offset_sectors = crypt_get_data_offset(cd);
475
0
    p_data_device = crypt_data_device(cd);
476
0
  }
477
478
0
  if (integrity_inline)
479
0
    dmdi.flags |= (CRYPT_ACTIVATE_NO_JOURNAL | CRYPT_ACTIVATE_INLINE_MODE);
480
481
0
  r = dm_integrity_target_set(cd, tgt, 0, dmdi.size, p_metadata_device,
482
0
      p_data_device, crypt_get_integrity_tag_size(cd),
483
0
      data_offset_sectors, crypt_get_sector_size(cd), integrity_key,
484
0
      journal_crypt_key, journal_mac_key, params);
485
0
  if (r < 0)
486
0
    goto err;
487
488
0
  log_dbg(cd, "Trying to format INTEGRITY device on top of %s, tmp name %s, tag size %d%s.",
489
0
    device_path(tgt->data_device), tmp_name, tgt->u.integrity.tag_size, integrity_inline ? " (inline)" : "");
490
491
0
  r = device_block_adjust(cd, tgt->data_device, DEV_EXCL, tgt->u.integrity.offset, NULL, NULL);
492
0
  if (r < 0 && (dm_flags(cd, DM_INTEGRITY, &dmi_flags) || !(dmi_flags & DM_INTEGRITY_SUPPORTED))) {
493
0
    log_err(cd, _("Kernel does not support dm-integrity mapping."));
494
0
    r = -ENOTSUP;
495
0
  }
496
0
  if (r)
497
0
    goto err;
498
499
0
  if (tgt->u.integrity.meta_device) {
500
0
    r = device_block_adjust(cd, tgt->u.integrity.meta_device, DEV_EXCL, 0, NULL, NULL);
501
0
    if (r)
502
0
      goto err;
503
0
  }
504
505
0
  r = dm_create_device(cd, tmp_name, CRYPT_INTEGRITY, &dmdi);
506
0
  if (r)
507
0
    goto err;
508
509
0
  r = dm_remove_device(cd, tmp_name, CRYPT_DEACTIVATE_FORCE);
510
0
  if (r)
511
0
    goto err;
512
513
  /* reload sb_flags from superblock (important for SB_FLAG_INLINE) */
514
0
  if (sb_flags)
515
0
    r = INTEGRITY_read_sb(cd, NULL, sb_flags);
516
0
err:
517
0
  dm_targets_free(cd, &dmdi);
518
0
  if (reduced_device) {
519
    dm_remove_device(cd, reduced_device_name, CRYPT_DEACTIVATE_FORCE);
520
0
    device_free(cd, reduced_device);
521
0
  }
522
0
  return r;
523
0
}