dm-integrity — 블록 레벨 무결성(Integrity) 보호

dm-integrity는 Device Mapper 프레임워크 위에서 읽기/쓰기 모두 지원하는 블록 레벨 무결성 보호를 제공합니다. 저널링(Journaling) 기반 무결성 태그로 crash-safety를 보장하며, Bitmap 모드로 대용량 디스크에서 성능을 최적화하고, dm-crypt와 AEAD 결합으로 암호화(Encryption)+무결성을 단일 레이어에서 달성합니다. 커널 내부 구현(drivers/md/dm-integrity.c)을 심층 분석합니다.

관련 Device Mapper 문서:
  • dm-verity — Merkle Tree 기반 읽기 전용(Read-Only) 블록 검증
  • dm-crypt — 블록 레벨 투명 암호화, LUKS, 성능 최적화
  • Device Mapper / LVM — DM 프레임워크 기본
전제 조건: 블록 I/O 문서와 Device Mapper / LVM 문서를 먼저 읽으세요. 블록 디바이스의 기본 I/O 경로와 Device Mapper의 target 개념을 이해해야 합니다.
일상 비유: dm-integrity는 은행 금고의 감사 로그와 비슷합니다. 금고에 물건을 넣거나 뺄 때마다 기록(무결성 태그)을 남기고, 기록이 변조되지 않았는지 실시간(Real-time)으로 검증합니다. 저널링은 정전이 발생해도 기록의 일관성을 보장하는 안전 장치입니다.

핵심 요약

  • Device Mapper — 블록 디바이스 위에 가상 매핑(Mapping) 레이어를 제공하는 커널 프레임워크
  • dm-integrity — 읽기/쓰기 모두 지원하는 무결성 태그 + 저널링
  • Journal 모드 — 데이터+태그를 저널에 먼저 기록하여 crash-safety 완전 보장
  • Bitmap 모드 — dirty 블록 추적으로 성능 향상, crash 후 부분 재검증
  • AEAD 결합 — dm-crypt + dm-integrity를 결합한 인증 암호화 (AES-GCM)

단계별 이해

  1. Device Mapper 레이어 이해
    커널의 블록 I/O 스택에서 DM은 bio를 가로채 target 드라이버에 전달합니다.
  2. dm-integrity로 실시간 무결성
    매 쓰기마다 무결성 태그를 계산하고 저널에 기록하여 crash-safety를 보장합니다.
  3. 저널링과 crash-safety
    저널은 순환 버퍼(Buffer)로 데이터+태그를 원자적(Atomic)으로 기록하며, 정전 후에도 일관성을 유지합니다.
  4. AEAD 모드로 암호화 결합
    dm-crypt + dm-integrity AEAD 모드로 암호화와 무결성을 동시에 달성합니다.

Device Mapper 연결 지점

이 문서는 Device Mapper 전체 구조를 반복 설명하기보다 dm-integrity target이 DM 코어와 만나는 지점만 짚습니다. DM의 공통 자료구조(mapped_device, dm_table, dm_target)와 ioctl 흐름은 Device Mapper / LVM 문서에서 먼저 확인하세요.

공통 DM 배경: target 등록, map() 반환값, suspend/resume, table 교체 같은 공통 동작은 Device Mapper / LVM 문서로 모았습니다.

dm-integrity 개요

dm-integrity는 읽기/쓰기 모두 지원하는 블록 레벨 무결성 보호를 제공합니다. dm-verity와 달리 쓰기 시에도 무결성 태그를 자동으로 계산하여 저장하며, 저널링을 통해 crash-safety를 보장합니다. 커널 소스 drivers/md/dm-integrity.c에 구현되어 있으며, Journal, Bitmap, Direct(no-journal) 세 가지 모드를 제공합니다.

Superblock magic, version Journal commit 단위 기록 Data 0 4096B Tag0 32B Data 1 Tag1 ... Data N TagN dm-integrity 동작 모드 Journal 모드 (기본): 데이터+태그를 저널에 먼저 기록 후 실제 위치로 commit Bitmap 모드: 비트맵으로 dirty 블록 추적, crash 후 dirty 블록만 재검증 No-journal 모드: 저널 없이 직접 기록 (dm-crypt AEAD 모드 전용)
/* drivers/md/dm-integrity.c */
struct dm_integrity_c {
    struct dm_dev *dev;
    struct dm_dev *meta_dev;       /* 별도 메타 디바이스 (선택) */
    unsigned int tag_size;          /* 태그 크기 (바이트) */
    unsigned int sectors_per_block; /* 블록당 섹터 수 */
    __u64 provided_data_sectors;    /* 사용 가능 데이터 섹터 */
    unsigned int journal_entries;
    unsigned int journal_sections;
    struct journal_entry *journal;
    struct crypto_shash *internal_hash;
    struct crypto_shash *journal_mac;
    enum integrity_mode mode;  /* J, B, D (journal/bitmap/direct) */
    bool recalculate;
};
모드저널Crash Safety성능주요 용도
Journal (J)전체 저널완전 보장쓰기 2x 오버헤드(Overhead)단독 사용
Bitmap (B)비트맵(Bitmap)만dirty 블록 재검증저널보다 빠름대용량 디스크
Direct (D)없음비보장최소 오버헤드AEAD 모드 전용

저널링 메커니즘

dm-integrity의 저널은 데이터와 무결성 태그를 원자적(Atomic)으로 기록하여 crash-safety를 보장합니다. 저널은 순환 버퍼(circular buffer)로 구현되며, commit 단위로 데이터+태그 쌍을 기록합니다. Bitmap 모드에서는 저널 대신 비트맵으로 dirty 블록을 추적하며, crash 후 복구 시 dirty 비트가 설정된 블록만 재검증합니다.

/* drivers/md/dm-integrity.c - 저널 엔트리 구조 */
struct journal_entry {
    union {
        struct {
            __le64 sector;       /* 대상 섹터 번호 */
            __le32 data_csum;    /* 데이터 체크섬 */
            __le32 tag_csum;     /* 태그 체크섬 */
        } s;
        __le64 commit_id;         /* 커밋 ID */
    } u;
};
저널 크기 계산: 기본 저널 크기는 약 64MB이며, --journal-watermark 옵션으로 저널 flush 임계값을 설정할 수 있습니다. 저널이 가득 차면 쓰기가 차단되므로, 워크로드에 맞는 적절한 크기 설정이 중요합니다.

dm-integrity 설정

dm-integrity는 integritysetup 도구로 설정합니다. cryptsetup 패키지에 포함되어 있으며, 포맷 후 활성화하는 2단계로 진행합니다.

# 1. dm-integrity 포맷 (SHA-256 해시, 저널 모드)
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --tag-size 32 \
    --sector-size 4096 \
    --journal-size 64M

# 2. dm-integrity 디바이스 활성화
$ integritysetup open /dev/sdb1 integrity_disk \
    --integrity sha256

# 3. 파일시스템 생성 및 마운트
$ mkfs.ext4 /dev/mapper/integrity_disk
$ mount /dev/mapper/integrity_disk /mnt/secure

# 4. Bitmap 모드로 설정 (성능 우선)
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --integrity-bitmap-mode \
    --bitmap-sectors-per-bit 65536

$ integritysetup open /dev/sdb1 integrity_bitmap \
    --integrity sha256 \
    --integrity-bitmap-mode
옵션설명기본값
--integrity해시(Hash) 알고리즘sha256
--tag-size무결성 태그 크기 (바이트)알고리즘 출력 크기
--sector-size섹터 크기512
--journal-size저널 크기자동 계산
--journal-watermark저널 flush 임계값 (%)50%
--journal-commit-time저널 commit 주기 (ms)10000
--integrity-bitmap-mode비트맵 모드 사용비활성

dm-integrity 커널 내부 구현

dm-integrity의 커널 구현은 drivers/md/dm-integrity.c에 있으며, 약 4,500줄에 달하는 복잡한 코드입니다. 핵심 동작은 bio를 분할하여 데이터와 무결성 태그를 동시에 처리하는 것입니다.

/* drivers/md/dm-integrity.c - map 함수 */
static int integrity_map(struct dm_target *ti, struct bio *bio)
{
    struct dm_integrity_c *ic = ti->private;
    struct dm_integrity_io *dio;

    dio = dm_per_bio_data(bio, ti->per_io_data_size);
    dio->ic = ic;
    dio->bi_sector = bio->bi_iter.bi_sector;
    dio->bi_integrity = bio_integrity(bio);

    if (bio_data_dir(bio) == WRITE) {
        if (ic->mode == 'J') {
            /* Journal 모드: 저널에 먼저 기록 */
            integrity_journal_write(ic, dio);
        } else if (ic->mode == 'B') {
            /* Bitmap 모드: dirty 비트 설정 */
            integrity_bitmap_write(ic, dio);
        }
        /* 무결성 태그 계산 및 저장 */
        integrity_metadata_write(ic, dio);
    } else {
        /* READ: 데이터 읽기 후 태그 검증 */
        integrity_metadata_read(ic, dio);
    }

    /* bio를 데이터 디바이스로 remap */
    bio_set_dev(bio, ic->dev->bdev);
    bio->bi_iter.bi_sector = integrity_data_sector(ic, dio->bi_sector);

    return DM_MAPIO_REMAPPED;
}

/* 무결성 태그 검증 (읽기 시) */
static int integrity_check_tag(struct dm_integrity_c *ic,
                               u8 *data, u8 *stored_tag,
                               sector_t sector)
{
    u8 computed_tag[MAX_TAG_SIZE];
    int r;

    /* 해시 계산 */
    r = crypto_shash_digest(ic->internal_hash_desc,
                            data, ic->sectors_per_block << SECTOR_SHIFT,
                            computed_tag);
    if (r)
        return r;

    /* 저장된 태그와 비교 */
    if (memcmp(computed_tag, stored_tag, ic->tag_size)) {
        DMERR("integrity checksum failed at sector %llu",
              (unsigned long long)sector);
        return -EILSEQ;
    }
    return 0;
}

dm-integrity의 I/O 경로에서 주의할 점은 메타데이터 I/O입니다. 데이터와 태그가 디스크 상에서 인터리빙되어 있기 때문에, 연속적인 데이터 I/O가 비연속적인 디스크 I/O로 변환될 수 있습니다. 이로 인해 HDD에서는 성능 저하가 심각할 수 있으며, SSD에서는 상대적으로 영향이 적습니다. 별도의 메타 디바이스(--data-device)를 사용하면 이 문제를 완화할 수 있습니다.

dm-integrity의 내부 동작을 더 깊이 살펴보면, 데이터 섹터와 태그 영역의 매핑이 핵심입니다. 논리 섹터 번호를 물리 섹터 번호로 변환할 때, 태그 영역의 크기를 고려하여 오프셋(Offset)을 계산합니다. 이 변환은 get_data_sector() 함수에서 수행됩니다.

/* drivers/md/dm-integrity.c - 섹터 변환 */
static sector_t get_data_sector(struct dm_integrity_c *ic,
                                 area_t area, sector_t offset)
{
    sector_t result;

    /* 데이터 영역과 태그 영역이 인터리빙됨
     * area = 논리 섹터 / interleave_sectors
     * offset = 논리 섹터 % interleave_sectors */
    result = area * (ic->interleave_sectors + ic->tag_area_sectors);
    result += ic->initial_sectors;  /* 슈퍼블록 + 저널 공간 */
    result += offset;

    return result;
}

/* 태그 읽기/쓰기 - 메타데이터 I/O */
static void rw_tag(struct dm_integrity_c *ic,
                   unsigned int tag_offset,
                   u8 *tag, unsigned int tag_size,
                   int op)
{
    sector_t tag_sector;
    unsigned int tag_sector_offset;

    /* 태그의 디스크 위치 계산 */
    tag_sector = tag_offset / (ic->tag_size * ic->journal_section_entries);
    tag_sector_offset = tag_offset % (ic->tag_size * ic->journal_section_entries);

    if (op == TAG_READ)
        dm_bufio_read(ic->tag_bufio, tag_sector, ...);
    else
        dm_bufio_write_dirty(ic->tag_bufio, tag_sector, ...);
}
인터리빙 오버헤드: 데이터와 태그가 인터리빙되면 연속 읽기가 비연속 디스크 I/O로 변환됩니다. HDD에서는 이 패턴이 심각한 성능 저하를 유발합니다. SSD에서도 어느 정도의 오버헤드가 발생하며, 정확한 수치는 하드웨어·워크로드에 따라 달라집니다. 별도 메타 디바이스(--data-device)를 사용하면 데이터와 태그를 물리적으로 분리하여 이 문제를 완화할 수 있습니다.
# 별도 메타 디바이스로 dm-integrity 설정 (성능 최적화)
$ integritysetup format --data-device /dev/nvme0n1p1 /dev/nvme1n1p1     --integrity sha256 --sector-size 4096

# 결과: 데이터는 nvme0n1p1, 태그는 nvme1n1p1에 저장
# 두 NVMe를 병렬로 사용하여 성능 극대화
dm-integrity 커널 함수역할호출 시점
integrity_ctr()target 생성, 슈퍼블록(Superblock) 로드테이블 로드
integrity_dtr()target 해제, 저널 flush디바이스 제거
integrity_map()bio remap + 태그 처리매 I/O
integrity_end_io()읽기 태그 검증읽기 완료
do_journal_write()저널 -> 실데이터 플러시(Flush)저널 watermark
integrity_recalc()기존 데이터 태그 재계산recalculate 모드
integrity_bitmap_flush()비트맵 dirty 플러시bitmap 모드

dm-crypt + dm-integrity 결합

dm-crypt와 dm-integrity를 결합하면 인증 암호화(AEAD)를 구현할 수 있습니다. AES-GCM 또는 ChaCha20-Poly1305 같은 AEAD 알고리즘을 사용하여 암호화와 무결성 검증을 단일 연산으로 수행합니다. 이 결합 모드에서 dm-crypt는 암호화 담당, dm-integrity는 인증 태그 저장 담당입니다.

평문 데이터 dm-crypt (AEAD) AES-GCM: 암호화 + 인증 태그 생성 암호문 (ciphertext) 인증 태그 (16B) dm-integrity 데이터 + 태그를 디스크 기록 AEAD 모드 설정 명령어 cryptsetup luksFormat --type luks2 --cipher aes-gcm-random --integrity aead --sector-size 4096 --key-size 256 /dev/sdb dm-crypt가 자동으로 dm-integrity 레이어를 생성하여 태그 저장
# AEAD 모드 LUKS2 볼륨 생성 (AES-GCM)
$ cryptsetup luksFormat --type luks2 \
    --cipher aes-gcm-random \
    --integrity aead \
    --sector-size 4096 \
    --key-size 256 \
    /dev/sdb1

# HMAC 무결성 (비 AEAD, 별도 해시)
$ cryptsetup luksFormat --type luks2 \
    --cipher aes-xts-plain64 \
    --integrity hmac-sha256 \
    --sector-size 4096 \
    --key-size 512 \
    /dev/sdb1

# dm-integrity 디바이스가 자동 생성됨
$ dmsetup ls
encrypted        (254:1)
encrypted_dif    (254:0)  # dm-integrity 레이어

# 상태 확인
$ cryptsetup status encrypted
  type:    LUKS2
  cipher:  aes-gcm-random
  keysize: 256 bits
  integrity: aead
  integrity keysize: 0 bits  # AEAD는 별도 키 불필요
모드암호화무결성태그 크기성능 오버헤드
AES-GCM (AEAD)AES-GCMGCM 내장16B중간
AES-XTS + HMACAES-XTSHMAC-SHA25632B높음
ChaCha20-Poly1305ChaCha20Poly130516B중간
AES-XTS 단독AES-XTS없음0B낮음
AEAD 모드 주의사항: AEAD 모드에서는 dm-integrity가 no-journal 모드로 동작합니다. 정전 시 데이터+태그 불일치로 해당 섹터가 읽기 불가능해질 수 있습니다. UPS 또는 배터리 백업이 없는 환경에서는 저널 모드가 권장됩니다.

저널 Write-Ahead 로깅 상세

dm-integrity의 저널은 Write-Ahead Logging(WAL) 패턴을 따릅니다. 데이터와 무결성 태그를 실제 위치에 쓰기 전에 저널 영역에 먼저 기록함으로써 원자성(Atomicity)을 보장합니다. 정전 시 저널을 재생(replay)하여 일관성을 복구합니다.

dm-integrity 저널 Write-Ahead 로깅 흐름 WRITE bio 데이터 + 섹터 번호 1. 무결성 태그 계산 SHA-256(data) = tag 2. 저널에 원자적 기록 journal_entry: sector + data + tag commit_id로 트랜잭션 경계 표시 저널 순환 버퍼 (Circular Buffer) Section 0 (committed) Section 1 (committed) Section 2 (writing) Section 3 (free) ... Section N flush_head write_head 3. 실제 위치에 Flush do_journal_write(): 저널 -> 데이터+태그 영역 Flush 트리거 조건 journal_watermark(50%) 또는 journal_commit_time(10s) 4. 저널 섹션 해제 (reclaim) flush_head 전진, 공간 재사용 Crash Recovery (정전 복구) 1. 슈퍼블록에서 마지막 commit_id 확인 2. 유효한 commit_id를 가진 저널 엔트리만 선택하여 replay 3. 미완료 트랜잭션은 폐기 (commit_id 불일치) 4. 저널 MAC 검증 (journal_mac 설정 시)
/* drivers/md/dm-integrity.c - 저널 쓰기 핵심 경로 */

/* 저널 섹션 헤더 */
struct journal_section_header {
    __le64 commit_id;   /* 트랜잭션 식별자 */
    __le64 commit_seq;  /* 순서 번호 */
};

/* 저널 커밋 처리 */
static void integrity_commit(struct dm_integrity_c *ic)
{
    struct journal_section_header *header;
    unsigned int section;

    section = ic->free_section;
    header = journal_section_header(ic, section);

    /* commit_id 기록 (홀수 = 커밋 완료 표시) */
    header->commit_id = cpu_to_le64(ic->commit_id);
    header->commit_seq = cpu_to_le64(ic->commit_seq);

    /* 저널 MAC 계산 (선택적 보호) */
    if (ic->journal_mac) {
        crypto_shash_digest(ic->journal_mac_desc,
                            (u8 *)header,
                            ic->journal_section_size,
                            header->mac);
    }

    /* 저널 섹션을 디스크에 동기 쓰기 */
    write_journal_section(ic, section);
    flush_journal(ic);

    ic->commit_seq++;
}

/* 저널 -> 실데이터 영역 플러시 */
static void do_journal_write(struct dm_integrity_c *ic)
{
    unsigned int section, entry;

    /* committed 섹션을 순서대로 처리 */
    while (ic->flush_section != ic->free_section) {
        section = ic->flush_section;

        /* 섹션 내 각 엔트리 처리 */
        for (entry = 0; entry < ic->journal_section_entries; entry++) {
            struct journal_entry *je;
            sector_t sector;
            u8 *data, *tag;

            je = journal_entry_get(ic, section, entry);
            sector = le64_to_cpu(je->u.s.sector);

            /* 데이터를 실제 위치에 쓰기 */
            data = journal_data_get(ic, section, entry);
            integrity_write_data(ic, sector, data);

            /* 태그를 태그 영역에 쓰기 */
            tag = journal_tag_get(ic, section, entry);
            rw_tag(ic, sector, tag, ic->tag_size, TAG_WRITE);
        }

        /* 섹션 플러시 완료, flush_head 전진 */
        ic->flush_section = (section + 1) % ic->journal_sections;
    }
}
저널 MAC 보호: --journal-integrity 옵션으로 저널 자체의 무결성도 보호할 수 있습니다. 예: integritysetup format --journal-integrity hmac-sha256 이 옵션은 저널 섹션마다 HMAC을 추가하여 저널 변조를 탐지합니다. --journal-crypt 옵션으로 저널을 암호화하여 기밀성도 제공 가능합니다.

Bitmap 모드 vs Journal 모드 비교

dm-integrity는 Journal 모드와 Bitmap 모드 두 가지 crash-safety 메커니즘을 제공합니다. Journal 모드는 완전한 crash-safety를 보장하지만 쓰기 증폭(Write Amplification)이 발생하며, Bitmap 모드는 dirty 블록만 추적하여 성능을 개선하지만 crash 후 재검증이 필요합니다.

Journal 모드 vs Bitmap 모드 쓰기 경로 비교 Journal 모드 (기본) WRITE 요청: 데이터 D, 섹터 S 1. SHA-256(D) = Tag 계산 2. 저널에 기록 (1차 쓰기) {commit_id, sector, D, Tag} -> journal 3. 실제 위치에 기록 (2차 쓰기) 4. 저널 섹션 해제 장점: 완전한 crash-safety 단점: 쓰기 2x 증폭, 지연시간 증가 Bitmap 모드 WRITE 요청: 데이터 D, 섹터 S 1. SHA-256(D) = Tag 계산 2. 비트맵 dirty 비트 설정 bitmap[S/sectors_per_bit] = 1 3. 데이터+태그 직접 기록 (1차 쓰기만) 4. 비트맵 flush (주기적) 장점: 저널 대비 ~2x 빠른 쓰기 단점: crash 후 dirty 블록 재검증 필요 Crash 후 복구 비교 Journal: 저널 replay (수 초), 데이터 손실 없음 Bitmap: dirty 블록 재검증 (수 분~수 시간, 디스크 크기에 비례), 미커밋 쓰기 무효화
/* drivers/md/dm-integrity.c - Bitmap 모드 핵심 */

/* 비트맵 dirty 비트 설정 */
static void integrity_bitmap_mark_dirty(struct dm_integrity_c *ic,
                                         sector_t sector,
                                         sector_t n_sectors)
{
    unsigned long bit;
    unsigned long end_bit;

    /* 섹터를 비트맵 인덱스로 변환 */
    bit = sector / ic->sectors_per_bitmap_bit;
    end_bit = (sector + n_sectors - 1) / ic->sectors_per_bitmap_bit;

    /* dirty 비트 설정 (원자적) */
    while (bit <= end_bit) {
        if (!test_and_set_bit(bit, ic->dirty_bitmap))
            ic->dirty_bitmap_count++;
        bit++;
    }
}

/* Crash 후 비트맵 기반 재검증 */
static void integrity_bitmap_recalculate(struct dm_integrity_c *ic)
{
    unsigned long bit;

    /* dirty 비트가 설정된 블록만 재검증 */
    for (bit = 0; bit < ic->bitmap_size; bit++) {
        if (test_bit(bit, ic->dirty_bitmap)) {
            sector_t start = bit * ic->sectors_per_bitmap_bit;
            sector_t count = ic->sectors_per_bitmap_bit;

            /* 데이터를 읽고 태그를 재계산 */
            integrity_recalc_range(ic, start, count);

            /* 재검증 완료 후 dirty 비트 해제 */
            clear_bit(bit, ic->dirty_bitmap);
        }
    }
}
비교 항목Journal 모드Bitmap 모드No-journal (Direct)
쓰기 증폭2x (저널 + 실데이터)1x + 비트맵 I/O1x
Crash Safety완전 보장dirty 블록 재검증비보장
복구 시간수 초 (저널 replay)수 분~시간없음
메모리 사용저널 크기만큼비트맵 크기 (작음)최소
순차 쓰기 처리량(Throughput)기준 대비 크게 감소기준 대비 다소 감소기준 대비 약간 감소
랜덤 쓰기 IOPS기준 대비 크게 감소기준 대비 다소 감소기준 대비 약간 감소
주요 용도단독 사용, 데이터 보호대용량 디스크AEAD 결합 전용
Bitmap 모드 권장 시나리오: Bitmap 모드는 대용량 디스크(1TB 이상)에서 Journal 모드의 쓰기 증폭 오버헤드를 줄이려 할 때 적합합니다. --bitmap-sectors-per-bit 값을 크게 설정하면 비트맵 I/O가 줄지만 crash 후 재검증 범위가 넓어집니다. 기본값 65536은 각 비트가 약 32MB 범위를 커버합니다.

Inline 모드 — 커널 6.11+

커널 6.11에서 dm-integrity에 Inline 모드(--integrity-inline / I)가 추가되었습니다. 기존 Journal·Bitmap 모드는 데이터와 별도의 메타데이터 영역에 태그를 저장했기 때문에 "쓰기 1회 = 디바이스 I/O 2회"의 원천적 증폭이 존재했습니다. Inline 모드는 디바이스가 하드웨어 수준의 섹터별 보호 정보(PI/DIF/DIX, T10-PI)를 지원할 때, 블록 계층이 제공하는 섹터 부가 필드에 HMAC 태그를 직접 기록합니다. 6.12에서는 inline 모드에서도 재계산(recalculate)이 지원되어, 디바이스 포맷을 바꾸지 않고 알고리즘 교체가 가능해졌습니다.

모드태그 저장 위치쓰기 증폭crash-safety요구 조건
Journal(J)저널 영역 → 이후 메타데이터 영역2x (저널 + 데이터)완전 보장없음
Bitmap(B)별도 메타데이터 영역1.0~1.2x재검증 필요없음
Inline(I)하드웨어 PI/DIF 섹터 필드1.0x디바이스 의존T10-PI 지원 드라이브
None(D)없음 — AEAD 태그만1.0x불가dm-crypt AEAD 전용
# 6.11+: inline 모드로 통합 — PI 지원 NVMe에서 쓰기 증폭 제거
$ integritysetup format /dev/nvme0n1 \
    --integrity hmac-sha256 \
    --integrity-inline \
    --tag-size 8

$ integritysetup open /dev/nvme0n1 data --integrity hmac-sha256

# 디바이스 포맷(tag를 섹터 메타데이터로 수납) 확인
$ integritysetup dump /dev/nvme0n1
#   mode:       I   ← Inline
#   tag_size:   8   ← 섹터당 PI 필드에 저장

# 6.12+: inline 모드의 재계산 — 알고리즘 교체 시 데이터 유지
$ integritysetup format /dev/nvme0n1 \
    --integrity hmac-sha512 --integrity-inline \
    --integrity-recalculate
🧩

커널 내부 경로 요약:

  • struct superblock.flags에 SB_FLAG_INLINE(0x20)이 추가되었습니다.
  • 블록 계층의 bi_integrity 경로(T10-PI)와 공유하여, bio 제출 시 bio_integrity_alloc()으로 integrity payload를 준비하고 드라이버가 NVMe PRACT/PRCHK 또는 SCSI PI 필드로 전달합니다.
  • HMAC/CRC 계산은 여전히 dm-integrity가 수행하지만, 저널 라운드가 사라져 write path가 "한 번의 쓰기 + in-band tag"로 단축됩니다.
  • Inline 모드에서는 저널 섹션이 존재하지 않으므로 crash 후 unclean shutdown 시 드라이브 자체의 PI 메타데이터가 원자적 페어링을 보장해야 합니다. 비정상 전원 단절이 빈번한 환경에서는 Journal 모드를 유지하는 편이 안전합니다.

Inline 모드 덕분에 dm-crypt + dm-integrity(AEAD) 스택이 T10-PI 대응 기업용 NVMe에서 단일 I/O 비용으로 동작하게 되어, 저장소 암호화 + 무결성 조합의 IOPS 페널티가 크게 감소합니다.

내부 자료 구조 상세

dm-integrity의 커널 구현은 여러 핵심 자료 구조에 의존합니다. dm_integrity_c는 target 인스턴스의 전체 상태를 관리하며, dm_integrity_io는 개별 I/O 요청의 컨텍스트(Context)를 추적합니다. 디스크 상의 슈퍼블록 구조와 저널 엔트리 포맷도 중요한 구성 요소입니다.

/* drivers/md/dm-integrity.c - 슈퍼블록 디스크 포맷 */
struct superblock {
    __u8 magic[8];         /* "integrt\1" 매직 넘버 */
    __u8 version;            /* 포맷 버전 (현재 5) */
    __s8 log2_interleave_sectors;  /* 인터리빙 크기 (log2) */
    __le16 integrity_tag_size;  /* 태그 크기 (바이트) */
    __le32 journal_sections;    /* 저널 섹션 수 */
    __le64 provided_data_sectors;  /* 제공 가능 데이터 섹터 */
    __le32 flags;               /* SB_FLAG_* */
    __u8 log2_sectors_per_block;  /* 블록당 섹터 수 (log2) */
    __le64 recalc_sector;       /* 재계산 진행 위치 */
    __u8 pad[4];
    __u8 salt[SALT_SIZE];      /* HMAC salt */
};

#define SB_FLAG_HAVE_JOURNAL_MAC    0x1
#define SB_FLAG_RECALCULATING       0x2
#define SB_FLAG_DIRTY_BITMAP        0x4
#define SB_FLAG_FIXED_PADDING       0x8
#define SB_FLAG_FIXED_HMAC          0x10

/* dm_integrity_c 구조체 상세 필드 */
struct dm_integrity_c {
    struct dm_dev *dev;              /* 데이터 디바이스 */
    struct dm_dev *meta_dev;         /* 별도 메타 디바이스 (선택) */
    struct dm_target *ti;

    /* 해시/암호 */
    struct crypto_shash *internal_hash;  /* 태그 해시 알고리즘 */
    struct crypto_shash *journal_mac;   /* 저널 MAC 알고리즘 */
    struct crypto_skcipher *journal_crypt;  /* 저널 암호화 */

    /* 디스크 레이아웃 */
    unsigned int tag_size;           /* 태그 크기 (바이트) */
    unsigned int sectors_per_block;  /* 블록당 섹터 수 */
    __u64 provided_data_sectors;     /* 사용 가능 데이터 섹터 */
    sector_t initial_sectors;        /* SB + 저널 + 패딩 */
    sector_t metadata_run;           /* 태그 영역 크기 */
    __s8 log2_interleave_sectors;

    /* 저널 관련 */
    unsigned int journal_entries;
    unsigned int journal_sections;
    unsigned int journal_section_entries;
    unsigned int journal_section_sectors;
    unsigned int free_section;        /* 현재 쓰기 섹션 */
    unsigned int flush_section;       /* 플러시 대기 섹션 */
    __u64 commit_id;                  /* 현재 커밋 ID */
    __u64 commit_seq;                 /* 커밋 시퀀스 번호 */

    /* 비트맵 모드 */
    unsigned long *dirty_bitmap;      /* dirty 블록 비트맵 */
    unsigned long bitmap_size;        /* 비트맵 크기 (비트) */
    unsigned int sectors_per_bitmap_bit;

    /* dm-bufio 캐시 */
    struct dm_bufio_client *bufio;    /* 태그 블록 캐시 */
    struct dm_bufio_client *journal_bufio;

    /* 워크큐 */
    struct workqueue_struct *commit_wq;
    struct workqueue_struct *writer_wq;
    struct workqueue_struct *recalc_wq;

    /* 성능 카운터 */
    enum integrity_mode mode;  /* 'J', 'B', 'D' */
    bool recalculate;          /* 태그 재계산 진행 중 */
    atomic64_t number_of_mismatches;  /* 불일치 카운터 */
};

/* I/O별 컨텍스트 */
struct dm_integrity_io {
    struct dm_integrity_c *ic;
    sector_t bi_sector;       /* 원래 섹터 번호 */
    struct bio_integrity_payload *bi_integrity;
    struct completion comp;    /* I/O 완료 대기 */
    int bi_status;
    struct work_struct work;   /* 워크큐 처리용 */
    unsigned int range_start;
    unsigned int range_end;
};

AEAD 통합 상세

AEAD(Authenticated Encryption with Associated Data) 결합 모드에서 dm-crypt는 인증 암호화를 수행하고, dm-integrity는 인증 태그의 저장/조회만 담당합니다. 이 구조에서 dm-integrity는 no-journal(Direct) 모드로 동작하며, 태그 계산을 dm-crypt의 AEAD 알고리즘에 위임합니다.

/* dm-crypt + dm-integrity AEAD 결합 내부 동작 */

/* dm-crypt가 AEAD 모드로 bio를 처리하는 경우:
 * 1. dm-crypt가 bio를 가로채 AEAD 암호화 수행
 * 2. 암호화 결과: ciphertext + authentication tag (16B for GCM)
 * 3. ciphertext는 bio 데이터에, auth tag는 bio_integrity에 저장
 * 4. dm-integrity가 bio_integrity에서 tag를 추출하여 디스크에 기록 */

/* drivers/md/dm-crypt.c - AEAD 처리 */
static int crypt_convert_block_aead(
    struct crypt_config *cc,
    struct convert_context *ctx,
    struct aead_request *req,
    unsigned int tag_offset)
{
    struct bio_integrity_payload *bip = ctx->bio_out->bi_integrity;
    u8 *tag;

    /* bio_integrity에서 태그 저장 위치 획득 */
    tag = bip->bip_buf + tag_offset;

    /* AEAD 요청 구성: src=평문, dst=암호문, assocdata=IV+섹터 */
    aead_request_set_crypt(req, &src, &dst,
                           cc->sector_size,  /* 평문 길이 */
                           iv);
    aead_request_set_ad(req, cc->integrity_iv_size);

    if (bio_data_dir(ctx->bio_out) == WRITE) {
        /* 암호화: 암호문 + 인증 태그 생성 */
        r = crypto_aead_encrypt(req);
        /* tag에 인증 태그가 저장됨 (GCM: 16바이트) */
    } else {
        /* 복호화: 인증 태그 검증 + 복호화 */
        r = crypto_aead_decrypt(req);
        /* 태그 불일치 시 -EBADMSG 반환 */
    }
    return r;
}

/* dm-integrity 측: AEAD 태그 수신/저장 */
/* integrity_map()에서 bio_integrity payload를 통해
 * dm-crypt가 생성한 인증 태그를 디스크에 기록
 * 읽기 시에는 디스크에서 태그를 읽어 bio_integrity에 채움 */
static void integrity_aead_tag_io(struct dm_integrity_c *ic,
                                   struct dm_integrity_io *dio,
                                   int rw)
{
    struct bio_integrity_payload *bip = dio->bi_integrity;
    u8 *tag_buf = bip ? bip->bip_buf : NULL;

    if (rw == WRITE && tag_buf) {
        /* dm-crypt가 생성한 인증 태그를 디스크에 기록 */
        rw_tag(ic, dio->bi_sector, tag_buf,
               ic->tag_size, TAG_WRITE);
    } else if (rw == READ && tag_buf) {
        /* 디스크에서 인증 태그를 읽어 bio_integrity에 전달 */
        rw_tag(ic, dio->bi_sector, tag_buf,
               ic->tag_size, TAG_READ);
    }
}
AEAD 알고리즘키 크기태그 크기특징
AES-GCM128/256비트16바이트AES-NI 가속, 널리 지원, nonce 재사용 취약
AES-CCM128/256비트16바이트소프트웨어 구현 용이, GCM보다 느림
ChaCha20-Poly1305256비트16바이트AES-NI 없는 환경에서 빠름, ARM 최적화
Aegis128128비트16바이트AES-NI 환경에서 GCM보다 빠름 (커널 6.0+)
AEAD no-journal 모드의 위험: AEAD 결합에서 dm-integrity는 no-journal 모드로 동작합니다. 정전 시 데이터는 갱신되었지만 태그는 이전 값인 상태가 될 수 있습니다. 이 경우 해당 섹터의 읽기 시 인증 실패(-EBADMSG)가 발생하며, 해당 데이터는 복구 불가능합니다. UPS 또는 배터리 백업이 필수적이며, 중요 데이터에는 별도 백업 전략이 필요합니다.

dm-integrity + LUKS2 연동

LUKS2(Linux Unified Key Setup version 2)는 dm-crypt와 dm-integrity를 통합 관리하는 상위 레벨 인터페이스를 제공합니다. cryptsetup을 사용하면 dm-integrity 레이어가 자동으로 생성되며, 키 관리와 무결성 설정을 단일 명령으로 처리할 수 있습니다.

# LUKS2 + dm-integrity 결합 설정 가이드

## 방법 1: AEAD 모드 (암호화 + 무결성 단일 연산)
$ cryptsetup luksFormat --type luks2 \
    --cipher aes-gcm-random \
    --integrity aead \
    --sector-size 4096 \
    --key-size 256 \
    /dev/sdb1

## 방법 2: HMAC 모드 (별도 해시, 더 강력한 무결성)
$ cryptsetup luksFormat --type luks2 \
    --cipher aes-xts-plain64 \
    --integrity hmac-sha256 \
    --sector-size 4096 \
    --key-size 512 \
    /dev/sdb1

# HMAC 모드에서는 키가 2개: 256-bit 암호화 + 256-bit HMAC
# --key-size 512 = AES-XTS(256) + HMAC-SHA256(256)

## 방법 3: Poly1305 (ARM/소프트웨어 환경 최적)
$ cryptsetup luksFormat --type luks2 \
    --cipher chacha20,aead \
    --integrity poly1305 \
    --sector-size 4096 \
    --key-size 256 \
    /dev/sdb1

## 볼륨 열기 및 마운트
$ cryptsetup open /dev/sdb1 secure_vol
$ mkfs.ext4 /dev/mapper/secure_vol
$ mount /dev/mapper/secure_vol /mnt/secure

## LUKS2 헤더에서 integrity 정보 확인
$ cryptsetup luksDump /dev/sdb1
# ...
# Segments:
#   0: crypt
#     cipher: aes-xts-plain64
#     integrity: hmac(sha256)
#     sector: 4096 [bytes]
# ...
# Integrity:
#   type: LUKS2_INTEGRITY
#   journal_size: 67108864
#   interleave_sectors: 32768

## dm-integrity 하위 디바이스 상태 확인
$ dmsetup ls
secure_vol      (254:1)
secure_vol_dif  (254:0)  # dm-integrity 레이어

$ dmsetup status secure_vol_dif
# 0 4194304 integrity 0 0

## LUKS2 + integrity 볼륨의 크기 오버헤드 확인
$ integritysetup dump /dev/sdb1
# Info for integrity device /dev/sdb1
#   superblock_version: 5
#   log2_interleave_sectors: 15
#   integrity_tag_size: 32
#   journal_sections: 88
#   provided_data_sectors: 4063232
#   sector_size: 4096

## 성능 비교: 무결성 없음 vs AEAD vs HMAC
# AES-XTS만 (무결성 없음)
$ cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 \
    --key-size 512 /dev/sdb1
# fio 순차 쓰기: ~1.8 GB/s

# AES-GCM AEAD
# fio 순차 쓰기: ~1.5 GB/s (약 17% 오버헤드)

# AES-XTS + HMAC-SHA256
# fio 순차 쓰기: ~1.2 GB/s (약 33% 오버헤드)
LUKS2 integrity 선택 가이드: AEAD 모드(AES-GCM)는 성능이 우수하지만 no-journal로 동작하여 crash 시 데이터 손실 가능성이 있습니다. HMAC 모드(AES-XTS + HMAC-SHA256)는 저널링으로 crash-safety를 보장하지만 성능 오버헤드가 큽니다. 서버 환경에서는 UPS + AEAD 모드가, 노트북/모바일에서는 HMAC 모드가 권장됩니다.

성능 최적화

dm-integrity의 성능은 저널 설정, 섹터 크기, 인터리빙 파라미터에 크게 좌우됩니다. 워크로드 특성에 맞는 적절한 튜닝이 필수적입니다.

# dm-integrity 성능 튜닝 가이드

## 1. journal_watermark 최적화
# 기본값 50%: 저널이 50% 차면 플러시 시작
# 높은 값: 배치 플러시로 처리량 증가, 지연시간 증가
# 낮은 값: 즉시 플러시, 지연시간 감소, 처리량 감소
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --journal-watermark 75 \
    --journal-commit-time 5000
# 75%에서 플러시, 또는 5초마다 강제 커밋

## 2. journal_commit_time 조정
# 기본값 10000ms (10초)
# 순차 쓰기 워크로드: 30000ms (30초) - 배치 효과 극대화
# OLTP 워크로드: 1000ms (1초) - 지연시간 최소화
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --journal-commit-time 1000

## 3. 저널 크기 최적화
# 큰 저널: 더 많은 쓰기를 배치 처리, 메모리 사용 증가
# 작은 저널: 빈번한 플러시, 쓰기 차단 가능
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --journal-size 256M
# 고부하 쓰기 워크로드에서 256MB 저널 권장

## 4. 섹터 크기 조정
# 4096B 섹터: 태그 오버헤드 비율 감소 (32B/4096B = 0.78%)
# 512B 섹터: 태그 오버헤드 비율 증가 (32B/512B = 6.25%)
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --sector-size 4096

## 5. 별도 메타 디바이스 사용 (최고 성능)
# 데이터와 태그를 물리적으로 분리하여 병렬 I/O
$ integritysetup format \
    --data-device /dev/nvme0n1p1 \
    /dev/nvme1n1p1 \
    --integrity sha256 \
    --sector-size 4096
# 데이터: NVMe 0, 태그: NVMe 1 (병렬 접근)

## 6. Bitmap 모드로 쓰기 성능 개선
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --integrity-bitmap-mode \
    --bitmap-sectors-per-bit 65536 \
    --bitmap-flush-time 5000

## 7. 벤치마크 실행
# 순차 쓰기
$ fio --name=seq_write --filename=/dev/mapper/integrity_disk \
    --rw=write --bs=128k --numjobs=4 --iodepth=16 \
    --size=4G --direct=1 --group_reporting

# 랜덤 쓰기 (저널 모드 병목 테스트)
$ fio --name=rand_write --filename=/dev/mapper/integrity_disk \
    --rw=randwrite --bs=4k --numjobs=4 --iodepth=32 \
    --size=1G --direct=1 --group_reporting

# 혼합 읽기/쓰기
$ fio --name=mixed --filename=/dev/mapper/integrity_disk \
    --rw=randrw --rwmixread=70 --bs=4k --numjobs=4 \
    --iodepth=32 --size=1G --direct=1 --group_reporting
튜닝 파라미터기본값순차 쓰기 최적랜덤 쓰기 최적영향
journal-watermark50%80%30%플러시 빈도 조절
journal-commit-time10000ms30000ms1000ms커밋 주기
journal-size자동256MB64MB배치 크기
sector-size512B4096B4096B태그 오버헤드 비율
bitmap-sectors-per-bit6553613107232768재검증 범위
성능 병목(Bottleneck) 진단: dm-integrity의 쓰기 성능이 기대보다 낮다면 저널 flush가 병목일 가능성이 높습니다. iostat -x 1로 디바이스 사용률을 확인하고, %util이 100%에 근접하면 저널 크기 확대 또는 Bitmap 모드 전환을 고려하세요. HDD에서는 인터리빙으로 인한 seek 오버헤드가 지배적이므로 --data-device 옵션으로 메타 디바이스를 분리하는 것이 가장 효과적입니다.

디버깅(Debugging) 및 모니터링

dm-integrity 문제 진단에는 dmsetup, integritysetup, 커널 로그, 그리고 성능 모니터링 도구를 활용합니다.

# 1. dm-integrity 상태 확인
$ dmsetup status integrity_disk
# 0 4194304 integrity 0 0
# 형식: start_sector num_sectors integrity mismatch_count provision_mode
# mismatch_count가 0이 아니면 무결성 오류 발생

# 2. integritysetup dump - 디스크 레이아웃 상세
$ integritysetup dump /dev/sdb1
# Info for integrity device /dev/sdb1
#   superblock_version   5
#   log2_interleave_sectors 15
#   integrity_tag_size   32
#   journal_sections     88
#   provided_data_sectors 4063232
#   sector_size          4096
#   flags                FIXED_HMAC

# 3. 커널 로그 확인
$ dmesg | grep -i integrity
# device-mapper: integrity: dm-0: started in journal mode
# device-mapper: integrity: dm-0: initialized with tag size 32
# device-mapper: integrity: dm-0: checksum failed at sector 12345

# 4. 무결성 오류 카운터 실시간 모니터링
$ watch -n 1 'dmsetup status integrity_disk | awk "{print \"mismatches:\", \$5}"'

# 5. I/O 성능 모니터링
# 실제 디바이스와 DM 디바이스의 I/O 비교
$ iostat -x 1 /dev/sdb /dev/dm-0
# sdb (물리)와 dm-0 (dm-integrity)의 처리량 차이 = 오버헤드

# 6. dmsetup table로 현재 설정 확인
$ dmsetup table integrity_disk
# 0 4063232 integrity /dev/sdb1 0 32 J 6 journal_sectors:180224 ...

# 7. 저널 상태 확인
# 저널 사용률 추정 (쓰기 지연과 상관)
$ dmsetup message integrity_disk 0 stats
# (커널 버전에 따라 지원 여부 다름)

# 8. ftrace로 dm-integrity I/O 경로 추적
$ echo function_graph | sudo tee /sys/kernel/tracing/current_tracer
$ echo integrity_map | sudo tee /sys/kernel/tracing/set_graph_function
$ echo 1 | sudo tee /sys/kernel/tracing/tracing_on
# ... I/O 발생 ...
$ cat /sys/kernel/tracing/trace
# integrity_map -> integrity_journal_write -> do_journal_write

# 9. 강제 무결성 검증 (전체 디스크)
# 모든 블록을 읽어 태그 검증
$ dd if=/dev/mapper/integrity_disk of=/dev/null bs=1M status=progress
# -EIO 발생 시 해당 블록에 무결성 오류 존재

# 10. 재계산 모드 활성화 (기존 데이터에 태그 추가)
$ integritysetup open /dev/sdb1 integrity_disk \
    --integrity sha256 --integrity-recalculate
# 백그라운드에서 모든 블록의 태그를 재계산
# 진행 상황: dmesg에서 확인
증상원인해결 방법
읽기 시 -EIO (EILSEQ)데이터-태그 불일치해당 블록 재기록, 백업에서 복구
쓰기 속도 급감저널 full, 플러시 차단journal-size 증가, watermark 조정
부팅 시 오래 걸림bitmap 모드 재검증sectors-per-bit 증가, 또는 journal 모드
recalculate 완료 안됨대용량 디스크 재계산정상 동작, 백그라운드 진행 대기
mismatch_count 증가하드웨어 결함 또는 AEAD 미커밋SMART 확인, UPS 사용, 백업
메타 디바이스 오류태그 디바이스 장애메타 디바이스 교체, 재포맷

에러 복구 메커니즘

dm-integrity의 에러 복구는 모드에 따라 다르게 동작합니다. Journal 모드는 crash-safety가 보장되므로 저널 replay로 복구되지만, Bitmap 모드와 No-journal 모드에서는 데이터 손실이 발생할 수 있습니다.

dm-integrity Crash 후 복구 흐름 integritysetup open (장치 활성화) 슈퍼블록 읽기 및 플래그 확인 Journal 모드 복구 1. 저널 섹션 스캔 2. 유효한 commit_id 엔트리 선택 3. 저널 -> 실데이터 replay 4. 완전 복구 (데이터 손실 없음) Bitmap 모드 복구 1. dirty 비트맵 로드 2. dirty 비트 설정된 블록 읽기 3. 해시 재계산 및 태그 갱신 4. 부분 데이터 손실 가능 (미커밋 쓰기) No-journal (Direct) 복구 1. 복구 메커니즘 없음 2. 데이터-태그 불일치 섹터 존재 3. 해당 섹터 읽기 시 -EIO 정상 동작 재개 재검증 후 동작 재개 손상 섹터 접근 불가 복구 시간: Journal: 수 초 Bitmap: 수 분~시간 (디스크 크기 비례) No-journal: 복구 불가
/* drivers/md/dm-integrity.c - 저널 replay 구현 */
static void replay_journal(struct dm_integrity_c *ic)
{
    unsigned int section;
    __u64 last_commit_id = 0;

    /* 모든 저널 섹션을 스캔 */
    for (section = 0; section < ic->journal_sections; section++) {
        struct journal_section_header *header;
        header = journal_section_header(ic, section);

        /* commit_id 유효성 검증 */
        if (le64_to_cpu(header->commit_id) != ic->commit_id)
            continue;  /* 미완료 트랜잭션: 건너뜀 */

        /* journal MAC 검증 (설정된 경우) */
        if (ic->journal_mac) {
            r = verify_journal_mac(ic, section);
            if (r) {
                DMWARN("journal MAC mismatch in section %u",
                       section);
                continue;  /* 변조된 저널 엔트리 */
            }
        }

        /* 유효한 엔트리를 실제 위치에 replay */
        for (entry = 0; entry < ic->journal_section_entries; entry++) {
            struct journal_entry *je;
            je = journal_entry_get(ic, section, entry);

            if (journal_entry_is_unused(je))
                break;

            /* 데이터를 실제 위치에 쓰기 */
            write_journal_entry_to_disk(ic, je, section, entry);
        }
    }

    /* commit_id 증가 (다음 사용 준비) */
    ic->commit_id++;
    write_superblock(ic);

    DMINFO("journal replay completed, %u sections replayed",
           replayed_count);
}

/* recalculate 모드 - 백그라운드 태그 재계산 */
static void integrity_recalc(struct work_struct *work)
{
    struct dm_integrity_c *ic =
        container_of(work, struct dm_integrity_c, recalc_work);
    sector_t sector = ic->sb->recalc_sector;

    while (sector < ic->provided_data_sectors) {
        u8 data_buf[PAGE_SIZE];
        u8 tag[MAX_TAG_SIZE];

        /* 데이터 블록 읽기 */
        integrity_read_data(ic, sector, data_buf);

        /* 해시 태그 계산 */
        crypto_shash_digest(ic->internal_hash_desc,
                            data_buf, ic->sectors_per_block << SECTOR_SHIFT,
                            tag);

        /* 태그 저장 */
        rw_tag(ic, sector, tag, ic->tag_size, TAG_WRITE);

        /* 진행 상황 슈퍼블록에 기록 (체크포인트) */
        if (!(sector % RECALC_CHECKPOINT_INTERVAL)) {
            ic->sb->recalc_sector = cpu_to_le64(sector);
            write_superblock(ic);
        }

        sector += ic->sectors_per_block;
        cond_resched();  /* 다른 작업에 양보 */
    }

    /* 재계산 완료 */
    ic->sb->flags &= ~cpu_to_le32(SB_FLAG_RECALCULATING);
    write_superblock(ic);
    DMINFO("integrity recalculation completed");
}
에러 복구 전략 권장: Journal 모드에서는 데이터 손실이 거의 없지만, Bitmap 및 No-journal 모드에서는 crash 시 데이터-태그 불일치가 발생할 수 있습니다. 불일치 발생 시 해당 섹터를 재기록하면 태그가 갱신되어 정상화됩니다. integrity-recalculate 옵션으로 전체 디스크의 태그를 재계산할 수도 있지만, 이는 기존 태그를 모두 덮어쓰므로 변조 탐지 기능이 일시적으로 무효화(Invalidation)됩니다.

dm-flakey / dm-dust

dm-flakey와 dm-dust는 테스트 및 결함 주입(fault injection)용 Device Mapper target입니다. 파일시스템(Filesystem)이나 스토리지 스택의 에러 처리 경로를 테스트하는 데 사용됩니다.

/* dm-flakey: 주기적으로 I/O 에러를 발생시킴 */
# 10초 정상 동작 후 5초간 모든 I/O에 에러 반환
$ echo "0 $(blockdev --getsz /dev/sdb1) flakey /dev/sdb1 0 10 5" | \
    dmsetup create flakey_disk

# 옵션: 쓰기만 실패, 읽기는 정상
$ echo "0 $(blockdev --getsz /dev/sdb1) flakey /dev/sdb1 0 10 5 \
    1 drop_writes" | dmsetup create flakey_write

# dm-flakey 커널 구현 (간략화)
static int flakey_map(struct dm_target *ti, struct bio *bio)
{
    struct flakey_c *fc = ti->private;
    unsigned elapsed = jiffies - fc->start_time;

    if (elapsed % (fc->up_interval + fc->down_interval)
        >= fc->up_interval) {
        /* down 구간: 에러 반환 또는 데이터 변조 */
        if (fc->drop_writes && bio_data_dir(bio) == WRITE) {
            bio_endio(bio);  /* 쓰기 무시 */
            return DM_MAPIO_SUBMITTED;
        }
        bio->bi_status = BLK_STS_IOERR;
        bio_endio(bio);
        return DM_MAPIO_SUBMITTED;
    }

    /* up 구간: 정상 통과 */
    flakey_map_bio(ti, bio);
    return DM_MAPIO_REMAPPED;
}
/* dm-dust: 특정 블록에 읽기 에러를 주입 */
# dm-dust 디바이스 생성
$ echo "0 $(blockdev --getsz /dev/sdb1) dust /dev/sdb1 0 4096" | \
    dmsetup create dusty_disk

# 특정 블록에 bad block 마킹
$ dmsetup message dusty_disk 0 addbadblock 100
$ dmsetup message dusty_disk 0 addbadblock 200

# bad block 활성화
$ dmsetup message dusty_disk 0 enable

# 블록 100, 200 읽기 시 -EIO 반환
# 파일시스템의 에러 핸들링 로직 테스트에 활용

# bad block 제거
$ dmsetup message dusty_disk 0 removebadblock 100
테스트 활용: dm-flakey는 ext4, Btrfs 등 파일시스템의 crash consistency 테스트에 널리 사용됩니다. xfstests의 많은 테스트 케이스가 dm-flakey를 사용하여 정전 시나리오를 시뮬레이션합니다.

최신 커널 변경사항 (6.12 ~ 6.14+)

커널 6.12에서 dm-integrity의 Inline 모드가 한층 완성되었고, 내부 자료 구조 검증과 MAC 관련 안정성 패치(Patch)가 추가되었습니다. 기존 문서의 Inline 모드 (6.11+) 섹션에서 6.11 기반을 설명했으므로, 여기서는 그 이후 변경사항을 다룹니다.

6.12: Inline 모드 재계산(recalculate) 지원

커널 6.12부터: dm-integrity Inline 모드(I)에서 재계산(recalculate) 기능이 지원됩니다. 기존에는 Journal/Bitmap 모드에서만 가능하던 온라인 재계산을 Inline 모드에서도 수행할 수 있습니다.

재계산이란 dm-integrity 디바이스를 포맷하지 않고도 기존 데이터에 대한 무결성 태그를 백그라운드에서 재생성하는 기능입니다. 무결성 알고리즘을 교체(예: crc32c → hmac-sha256)하거나, 태그가 없는 기존 디바이스를 무결성 보호 디바이스로 전환할 때 사용합니다. Inline 모드에서 재계산이 지원됨으로써 T10-PI 지원 NVMe 디바이스에서도 운영 중단 없이 무결성 알고리즘 마이그레이션이 가능해졌습니다.

# 6.12+: Inline 모드에서 재계산 활성화
# T10-PI 지원 NVMe에 crc32c로 포맷하고 재계산 활성화
$ integritysetup format /dev/nvme0n1 \
    --integrity crc32c \
    --integrity-inline \
    --integrity-recalculate

# 재계산 진행 상황 모니터링
$ integritysetup status /dev/mapper/data
#   mode:              I
#   recalculate:       in progress
#   integrity:         crc32c

# 재계산 완료 후 무결성 검증 가능 상태 확인
$ dmsetup status data
#   0 ...: integrity 0 0 0 0 0
#   (수정 섹터 수: 0 = 재계산 완료)

6.12: MAC 크기 및 해시 소화 크기 검증 강화

커널 6.12부터: dm-integrity 초기화 시 MAC(메시지 인증 코드, Message Authentication Code) 키 크기가 HASH_MAX_DIGESTSIZE를 초과하지 않도록 경계 검사가 추가되었습니다. 이로써 잘못 구성된 HMAC 알고리즘 조합에서 발생하던 잠재적 버퍼 경계 문제가 선제적으로 방어됩니다.

/* dm-integrity.c — 6.12 MAC 크기 검증 추가 (개념 요약) */
if (ic->tag_size > HASH_MAX_DIGESTSIZE) {
    ti->error = "Tag size too large";
    return -EINVAL;
}

6.12: IPE LSM — dm-verity와 연계한 무결성 정책 생태계 완성

커널 6.12에서 IPE(무결성 정책 강제, Integrity Policy Enforcement) LSM이 메인라인에 병합되었습니다. IPE는 dm-verity와 직접 연동하지만, dm-integrity 기반 무결성도 정책 체계에서 고려됩니다. dm-integrity + dm-crypt AEAD 스택으로 보호된 블록 디바이스 위의 파일시스템은 IPE 정책에서 dm_verity=YES 유사한 방식으로 나중 버전에서 참조될 기반이 마련되었습니다. 현재는 dm-verity 연동이 주요 경로이지만 dm-integrity 단독 운용 환경에서도 IMA/EVM와 결합하여 유사한 보호를 구현할 수 있습니다.

6.14: dm-crypt + dm-integrity 결합 모드 버그 수정

커널 6.14부터: dm-crypt와 dm-integrity가 AEAD 결합 모드로 운영될 때 무결성 시드(integrity seed) 초기화 로직의 결함이 수정되었습니다. 또한 atomic 플래그의 초기화 순서와 bio 이터레이터(iterator) 완전 초기화 문제가 함께 패치되었습니다. 이 수정은 dm-crypt 사이드에서 이루어졌지만 dm-integrity의 AEAD 경로를 통해 데이터 신뢰성에도 영향을 미칩니다.

6.14 이전 버전 주의: dm-crypt + dm-integrity AEAD 결합 모드를 커널 6.14 미만에서 사용하는 경우, 특정 워크로드에서 integrity seed가 올바르게 초기화되지 않아 AEAD 태그 검증 오류가 발생할 수 있습니다. 가능하면 6.14 이상 커널로 업그레이드하거나, cryptsetup 버전을 최신으로 유지하십시오.
내부 관련 문서:
외부 참고 자료: