dm-verity / dm-integrity / dm-crypt — Device Mapper 데이터 검증, 무결성(Integrity), 암호화(Encryption)

Linux Device Mapper는 블록 디바이스 위에 가상 레이어를 쌓아 데이터 검증(dm-verity), 무결성 보호(dm-integrity), 암호화(dm-crypt)를 투명하게 제공합니다. Android Verified Boot의 핵심인 dm-verity Merkle Tree, 저널링(Journaling) 기반 dm-integrity, LUKS/dm-crypt 블록 암호화, 그리고 이들의 결합(AEAD)까지 커널 내부 구현(drivers/md/)을 심층 분석합니다.

관련 문서: dm-verity/dm-integrity/dm-crypt는 Device Mapper 프레임워크 위에 동작합니다. 블록 I/O 스택은 블록 레이어, I/O 스케줄링은 I/O 스케줄러(Scheduler), 파일시스템(Filesystem) 연동은 파일시스템 개요, Device Mapper 기본은 Device Mapper / LVM 페이지(Page)를 참고하세요.
전제 조건: 블록 I/O 문서와 Device Mapper / LVM 문서를 먼저 읽으세요. 블록 디바이스의 기본 I/O 경로와 Device Mapper의 target 개념을 이해해야 합니다.
일상 비유: dm-verity는 택배 상자의 봉인 스티커와 비슷합니다. 상자(디스크 블록)를 열기 전에 봉인(해시(Hash))이 손상되지 않았는지 확인합니다. dm-integrity는 은행 금고의 감사 로그처럼 모든 기록의 변조를 실시간(Real-time) 감지하고, dm-crypt는 금고 자체의 잠금(Lock) 장치로 내용물을 암호화합니다.

핵심 요약

  • Device Mapper — 블록 디바이스 위에 가상 매핑(Mapping) 레이어를 제공하는 커널 프레임워크
  • dm-verity — Merkle Tree 기반 읽기 전용(Read-Only) 블록 무결성 검증 (Android Verified Boot 핵심)
  • dm-integrity — 읽기/쓰기 모두 지원하는 무결성 태그 + 저널링
  • dm-crypt — 블록 레벨 투명 암호화 (LUKS, AES-XTS, Adiantum)
  • AEAD 결합 — dm-crypt + dm-integrity를 결합한 인증 암호화 (AES-GCM)

단계별 이해

  1. Device Mapper 레이어 이해
    커널의 블록 I/O 스택에서 DM은 bio를 가로채 target 드라이버에 전달합니다.
  2. dm-verity로 읽기 검증
    빌드 시 Merkle Tree를 생성하고, 런타임에 블록 단위로 해시를 검증합니다.
  3. dm-integrity로 실시간 무결성
    매 쓰기마다 무결성 태그를 계산하고 저널에 기록하여 crash-safety를 보장합니다.
  4. dm-crypt로 암호화
    섹터 단위로 AES-XTS 암호화를 적용하여 디스크 데이터를 보호합니다.
  5. 결합 활용
    dm-crypt + dm-integrity AEAD 모드로 암호화와 무결성을 동시에 달성합니다.

Device Mapper 개요

Device Mapper(DM)는 Linux 커널의 블록 레이어에서 가상 블록 디바이스를 생성하는 프레임워크입니다. drivers/md/dm.c에 구현된 DM 코어는 struct mapped_device로 가상 디바이스를 표현하고, struct dm_table로 매핑 테이블을 관리합니다. 각 target 드라이버(struct target_type)는 특정 기능(암호화, 검증, 스냅샷 등)을 구현하며, DM 코어가 bio를 적절한 target에 라우팅(Routing)합니다.

DM은 원래 Heinz Mauelshagen이 LVM(Logical Volume Manager)을 위해 개발했으며, 현재는 dm-crypt, dm-verity, dm-integrity, dm-thin, dm-cache, dm-snapshot 등 다양한 target을 통해 리눅스 스토리지 스택의 핵심 계층으로 자리잡았습니다. 사용자 공간(User Space)에서는 dmsetup, cryptsetup, veritysetup, integritysetup 등의 도구로 DM 디바이스를 관리합니다.

사용자 공간: dmsetup / cryptsetup / veritysetup / integritysetup ioctl(DM_TABLE_LOAD) DM Core (dm.c) mapped_device / dm_table / dm_target dm-verity Merkle Tree 검증 dm-integrity 저널링 무결성 dm-crypt 블록 암호화 블록 레이어 (bio / request) 물리 블록 디바이스 (HDD / SSD / NVMe)

DM 코어의 핵심 자료 구조를 살펴봅니다:

/* include/linux/device-mapper.h */
struct target_type {
    uint64_t features;
    const char *name;
    struct module *module;

    int (*ctr)(struct dm_target *ti, unsigned argc, char **argv);
    void (*dtr)(struct dm_target *ti);
    int (*map)(struct dm_target *ti, struct bio *bio);
    int (*end_io)(struct dm_target *ti, struct bio *bio,
                  blk_status_t *error);
    void (*status)(struct dm_target *ti, status_type_t type,
                   unsigned status_flags, char *result,
                   unsigned maxlen);
    int (*iterate_devices)(struct dm_target *ti,
                          iterate_devices_callout_fn fn, void *data);
};

struct dm_target {
    struct dm_table *table;
    struct target_type *type;
    sector_t begin;          /* 매핑 시작 섹터 */
    sector_t len;            /* 매핑 길이 (섹터) */
    void *private;           /* target 별 사적 데이터 */
    unsigned num_discard_bios;
    unsigned num_secure_erase_bios;
    unsigned num_write_zeroes_bios;
};

DM의 I/O 경로는 다음과 같이 진행됩니다: 사용자 공간에서 가상 디바이스(/dev/dm-N)에 I/O를 발행하면, dm_submit_bio()가 호출됩니다. 이 함수는 dm_table에서 bio가 속하는 target을 찾아 해당 target의 map() 콜백(Callback)을 호출합니다. target은 bio를 변환(remap, split, encrypt 등)하여 하위 디바이스로 전달합니다. map() 콜백의 반환값에 따라 DM 코어가 bio의 후속 처리를 결정합니다: DM_MAPIO_REMAPPED는 bio가 하위 디바이스로 redirect되었음을, DM_MAPIO_SUBMITTED는 target이 bio를 완전히 처리했음을 의미합니다.

DM 구성 요소역할소스 파일
mapped_device가상 블록 디바이스 표현drivers/md/dm.c
dm_tabletarget 매핑 테이블drivers/md/dm-table.c
dm_target개별 매핑 세그먼트drivers/md/dm.h
dm_ioI/O 요청 추적drivers/md/dm.c
dm_ioctl사용자 공간 인터페이스drivers/md/dm-ioctl.c
dm_bufio해시/메타데이터 블록 캐시(Cache)drivers/md/dm-bufio.c
DM 테이블 형식: dmsetup으로 전달하는 테이블은 시작섹터 길이 target_name [target_args...] 형식입니다. 예: 0 2097152 verity 1 /dev/sda1 /dev/sda2 4096 4096 262144 1 sha256 root_hash salt
/* drivers/md/dm-ioctl.c - 주요 ioctl 명령 */
/* 사용자 공간 (dmsetup) <-> 커널 DM 인터페이스 */

/* DM_DEV_CREATE: 새 mapped_device 생성 */
/* DM_DEV_REMOVE: mapped_device 제거 */
/* DM_TABLE_LOAD: 매핑 테이블 로드 */
/* DM_DEV_SUSPEND: I/O 일시 중지 (테이블 교체 준비) */
/* DM_DEV_STATUS: 디바이스 상태 조회 */
/* DM_TABLE_STATUS: 테이블 상태 (dmsetup status) */
/* DM_TARGET_MSG: target에 메시지 전달 (dmsetup message) */

static int table_load(struct file *filp,
                      struct dm_ioctl *param,
                      size_t param_size)
{
    struct dm_table *t;
    struct mapped_device *md;

    /* 1. 테이블 생성 */
    r = dm_table_create(&t, get_mode(param), param->target_count, md);

    /* 2. 각 target 파싱 및 추가 */
    for (i = 0; i < param->target_count; i++) {
        dm_table_add_target(t, type_name, start, len, params);
        /* 내부에서 target_type->ctr() 호출 */
    }

    /* 3. 테이블 완료 (무결성 검증) */
    r = dm_table_complete(t);

    /* 4. 테이블을 mapped_device에 연결 */
    dm_swap_table(md, t);
    return 0;
}

/* bio 라우팅 - dm_submit_bio() 핵심 경로 */
static void dm_submit_bio(struct bio *bio)
{
    struct mapped_device *md = bio->bi_bdev->bd_disk->private_data;
    struct dm_table *map;
    struct dm_target *ti;

    map = dm_get_live_table(md, &srcu_idx);

    /* bio가 속하는 target 찾기 */
    ti = dm_table_find_target(map, bio->bi_iter.bi_sector);

    /* target의 map() 콜백 호출 */
    r = ti->type->map(ti, bio);

    switch (r) {
    case DM_MAPIO_SUBMITTED:
        break;  /* target이 처리 완료 */
    case DM_MAPIO_REMAPPED:
        submit_bio_noacct(bio);  /* 하위 디바이스로 전달 */
        break;
    case DM_MAPIO_REQUEUE:
        bio_io_error(bio);  /* 재시도 */
        break;
    }

    dm_put_live_table(md, srcu_idx);
}
DM 스택 쌓기: DM 디바이스 위에 다른 DM 디바이스를 쌓을 수 있습니다. 예: 물리 디바이스 -> dm-integrity -> dm-crypt -> ext4 이 경우 각 레이어의 map()이 순서대로 호출됩니다. 스택 깊이가 깊을수록 I/O 지연(Latency)이 증가하므로, AEAD 모드처럼 단일 레이어에서 암호화+무결성을 처리하는 것이 성능상 유리합니다.

dm-verity 개요

dm-verity는 읽기 전용 블록 디바이스의 무결성을 검증하는 Device Mapper target입니다. ChromeOS에서 최초 도입되어 Android Verified Boot(AVB)의 핵심 메커니즘으로 자리잡았습니다. 커널 소스 drivers/md/dm-verity-target.c에 구현되어 있으며, 빌드 시점에 생성된 Merkle Tree의 루트 해시를 런타임에 블록 단위로 검증합니다.

dm-verity의 핵심 원리는 간단합니다: 디스크의 모든 4KB 블록에 대해 SHA-256(또는 다른 해시) 값을 계산하고, 이 해시들을 다시 해싱하여 트리 구조를 만듭니다. 최종 루트 해시 하나만 신뢰할 수 있으면 전체 디스크의 무결성을 검증할 수 있습니다. 읽기 요청이 들어올 때마다 해당 블록의 해시를 Merkle Tree에서 확인하여 변조를 탐지합니다.

/* drivers/md/dm-verity.h */
struct dm_verity {
    struct dm_dev *data_dev;       /* 데이터 디바이스 */
    struct dm_dev *hash_dev;       /* 해시 트리 디바이스 */
    struct dm_target *ti;
    struct dm_verity_fec *fec;    /* FEC 옵션 */

    unsigned int data_dev_block_bits;  /* 데이터 블록 크기 비트 */
    unsigned int hash_dev_block_bits;  /* 해시 블록 크기 비트 */
    unsigned int hash_per_block_bits;  /* 블록당 해시 수 비트 */
    unsigned int levels;              /* 트리 레벨 수 */
    unsigned int digest_size;          /* 해시 출력 크기 */
    unsigned int salt_size;

    const char *alg_name;              /* "sha256" 등 */
    u8 *root_digest;                   /* 루트 해시 */
    u8 *salt;                           /* salt 값 */
    sector_t data_blocks;              /* 총 데이터 블록 수 */
    sector_t hash_start;               /* 해시 영역 시작 */
    sector_t hash_blocks;              /* 해시 블록 수 */

    enum verity_mode mode;  /* EIO, logging, restart, panic */
    unsigned int validated_blocks;
    struct workqueue_struct *verify_wq;
};
dm-verity 동작 모드: DM_VERITY_MODE_EIO(기본): 검증 실패 시 -EIO 반환, DM_VERITY_MODE_LOGGING: 로그만 기록하고 I/O 허용, DM_VERITY_MODE_RESTART: 시스템 재부팅, DM_VERITY_MODE_PANIC: 커널 패닉(Kernel Panic) 유발
속성dm-verity비교 대상
방향읽기 전용dm-integrity: 읽기/쓰기
검증 단위블록 (4KB 기본)파일 단위: fs-verity
해시 저장별도 파티션/영역dm-integrity: 인라인 태그
주요 용도Verified Boot, 컨테이너(Container) 이미지서버 디스크 무결성
오버헤드(Overhead)읽기 시 해시 계산 + 트리 탐색읽기/쓰기 모두 오버헤드
커널 소스dm-verity-target.cdm-integrity.c

Merkle Tree 구조

dm-verity의 Merkle Tree는 데이터 블록들의 해시를 계층적으로 구성합니다. 리프(leaf) 레벨에서 각 데이터 블록(4KB)의 해시(SHA-256 기준 32바이트)를 계산하고, 이 해시들을 하나의 해시 블록(역시 4KB)에 모아 다시 해싱합니다. 4KB 블록에 32바이트 해시가 128개 들어가므로, 각 레벨에서 팬아웃(fan-out)은 128입니다. 1GB 데이터의 경우 SHA-256 기준으로 약 8MB + 64KB + 512B + 32B = 약 8.06MB의 해시 공간이 필요하며, 이는 전체 데이터의 약 0.8%에 불과합니다.

Root Hash Hash L2[0] Hash L2[1] Hash L1[0] Hash L1[1] Hash L1[2] Hash L1[N] Block 0 Block 1 Block 2 ... Block N Merkle Tree 크기 계산 데이터: 1GB = 262,144 블록 (4KB) L0 해시: 262,144 x 32B = 8MB L1 해시: 2,048 x 32B = 64KB L2 해시: 16 x 32B = 512B
/* Merkle Tree 레벨별 블록 수 계산 (커널 내부) */
/* drivers/md/dm-verity-target.c: verity_ctr() */
static int verity_hash_levels(struct dm_verity *v)
{
    sector_t hash_position;
    int levels = 0;
    sector_t data_blocks = v->data_blocks;

    /* hash_per_block = 블록_크기 / digest_size
     * SHA-256, 4KB 블록: 4096/32 = 128 */
    while (data_blocks > 1) {
        data_blocks = DIV_ROUND_UP(data_blocks,
                     1 << v->hash_per_block_bits);
        levels++;
    }
    v->levels = levels;

    /* 각 레벨의 해시 블록 시작 위치 계산 */
    hash_position = 0;
    for (int i = levels - 1; i >= 0; i--) {
        v->hash_level_block[i] = hash_position;
        data_blocks = DIV_ROUND_UP(v->data_blocks,
                     (sector_t)1 << (v->hash_per_block_bits * (i + 1)));
        hash_position += data_blocks;
    }
    return 0;
}
해시 알고리즘 선택: SHA-256이 기본이지만, SHA-512(보안 강화), BLAKE2b(성능 우선), SHA-1(레거시)도 커널 Crypto API가 지원하면 사용 가능합니다. veritysetup format--hash 옵션으로 지정합니다.

dm-verity 설정

dm-verity를 설정하려면 먼저 veritysetup 도구로 Merkle Tree를 생성하고, 이를 DM 테이블에 로드합니다. veritysetup은 cryptsetup 패키지에 포함되어 있습니다. 설정 과정은 오프라인 해시 생성과 온라인 검증 활성화 2단계로 나뉩니다.

# 1. 데이터 디바이스에 대한 해시 트리 생성
$ veritysetup format /dev/sda1 /dev/sda2
VERITY header information for /dev/sda2
UUID:            a1b2c3d4-e5f6-7890-abcd-ef1234567890
Hash type:       1
Data blocks:     262144
Data block size: 4096
Hash block size: 4096
Hash algorithm:  sha256
Salt:            abcdef0123456789abcdef0123456789...
Root hash:       4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d...

# 2. dm-verity 디바이스 활성화
$ veritysetup open /dev/sda1 verified_root /dev/sda2 \
    4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d...

# 3. 읽기 전용으로 마운트
$ mount -o ro /dev/mapper/verified_root /mnt/verified

# 4. dmsetup으로 직접 설정하는 경우
$ echo "0 2097152 verity 1 /dev/sda1 /dev/sda2 4096 4096 \
    262144 1 sha256 \
    4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d... \
    abcdef0123456789abcdef0123456789..." | dmsetup create verified_root
필드설명예시 값
versionverity 포맷 버전 (현재 1)1
data_dev데이터 블록 디바이스/dev/sda1
hash_dev해시 트리 디바이스/dev/sda2
data_block_size데이터 블록 크기4096
hash_block_size해시 블록 크기4096
num_data_blocks데이터 블록 수262144
hash_start_block해시 데이터 시작 블록1
algorithm해시 알고리즘sha256
root_hashMerkle Tree 루트 해시64자 hex
salt해시 솔트 (선택)hex 또는 -
보안 주의: 루트 해시의 신뢰 앵커가 핵심입니다. Android에서는 vbmeta 파티션에 서명된 루트 해시를 저장하고, 부트로더(Bootloader)가 vbmeta의 서명을 검증합니다. 루트 해시가 변조되면 전체 검증 체인이 무력화됩니다.

커널 내부 구현

dm-verity의 커널 구현은 drivers/md/dm-verity-target.c에 있습니다. 핵심 함수는 verity_map()verity_end_io()이며, 워크큐 기반으로 해시 검증을 수행합니다. 읽기 bio가 도착하면 verity_map()이 bi_end_io를 교체하고 데이터 디바이스로 remap합니다. 데이터 읽기가 완료되면 verity_end_io()가 워크큐에서 해시 검증을 스케줄합니다.

READ bio verity_map() bi_end_io 설정 data_dev READ submit_bio_noacct verity_end_io() 워크큐 스케줄 verity_verify_io() [workqueue] 해시 트리 검증 검증 성공: bio 완료 검증 실패: -EIO
/* drivers/md/dm-verity-target.c */
static int verity_map(struct dm_target *ti, struct bio *bio)
{
    struct dm_verity *v = ti->private;
    struct dm_verity_io *io;

    /* 쓰기 요청은 거부 (읽기 전용) */
    if (bio_data_dir(bio) == WRITE) {
        bio->bi_status = BLK_STS_IOERR;
        bio_endio(bio);
        return DM_MAPIO_SUBMITTED;
    }

    io = dm_per_bio_data(bio, ti->per_io_data_size);
    io->v = v;
    io->orig_bi_end_io = bio->bi_end_io;
    io->block = bio->bi_iter.bi_sector >> (v->data_dev_block_bits - SECTOR_SHIFT);
    io->n_blocks = bio->bi_iter.bi_size >> v->data_dev_block_bits;

    bio->bi_end_io = verity_end_io;
    bio_set_dev(bio, v->data_dev->bdev);
    verity_prefetch_io(v, io);

    return DM_MAPIO_REMAPPED;
}

static void verity_end_io(struct bio *bio)
{
    struct dm_verity_io *io = dm_per_bio_data(bio, ...);

    if (bio->bi_status) {
        verity_finish_io(io, bio->bi_status);
        return;
    }

    INIT_WORK(&io->work, verity_work);
    queue_work(io->v->verify_wq, &io->work);
}

verity_verify_io()는 각 데이터 블록에 대해 bottom-up으로 Merkle Tree를 순회합니다: 데이터 블록의 해시를 계산하고, 이를 Level 0 해시와 비교한 뒤, Level 0 해시 블록의 해시를 Level 1과 비교하는 방식으로 루트까지 올라갑니다. 한 번 검증된 해시 블록은 dm_bufio 캐시에 보관되어 재검증을 피합니다.

/* drivers/md/dm-verity-target.c - verity_verify_io() 핵심 */
static int verity_verify_io(struct dm_verity_io *io)
{
    struct dm_verity *v = io->v;
    struct bvec_iter start;
    unsigned int b;

    for (b = 0; b < io->n_blocks; b++) {
        int r;
        sector_t cur_block = io->block + b;
        struct ahash_request *req = verity_io_hash_req(v, io);

        /* 1단계: 데이터 블록 해시 계산 */
        r = verity_hash_for_block(v, io, cur_block,
                                  verity_io_want_digest(v, io),
                                  &is_zero);
        if (unlikely(r < 0))
            return r;

        /* 2단계: Merkle Tree 검증 (bottom-up) */
        r = verity_verify_level(v, io, cur_block, 0,
                               verity_io_want_digest(v, io));
        if (r)
            return r;
    }
    return 0;
}

/* 해시 블록 prefetch - 성능 최적화 핵심 */
static void verity_prefetch_io(struct dm_verity *v,
                               struct dm_verity_io *io)
{
    int i;

    for (i = v->levels - 1; i >= 0; i--) {
        sector_t hash_block_start, hash_block_end;

        /* 이 bio가 접근할 해시 블록 범위 계산 */
        hash_block_start = io->block >> (v->hash_per_block_bits * (i + 1));
        hash_block_end = (io->block + io->n_blocks - 1) >>
                         (v->hash_per_block_bits * (i + 1));

        hash_block_start += v->hash_level_block[i];
        hash_block_end += v->hash_level_block[i];

        /* dm_bufio를 통해 비동기 prefetch */
        dm_bufio_prefetch(v->bufio, hash_block_start,
                          hash_block_end - hash_block_start + 1);
    }
}
check_at_most_once 최적화: check_at_most_once 옵션을 활성화하면 각 블록이 최대 1회만 검증됩니다. 검증된 블록은 비트맵(Bitmap)으로 추적되어 재검증을 건너뜁니다. 이 옵션은 신뢰할 수 있는 스토리지(예: 읽기 전용 미디어)에서 성능을 크게 개선하지만, 메모리를 추가로 소비합니다. 1TB 디바이스 기준 약 32MB의 비트맵 메모리가 필요합니다.
/* dm-verity target 등록 - 모듈 초기화 */
static struct target_type verity_target = {
    .name    = "verity",
    .version = {1, 9, 0},
    .module  = THIS_MODULE,
    .ctr     = verity_ctr,
    .dtr     = verity_dtr,
    .map     = verity_map,
    .status  = verity_status,
    .prepare_ioctl = verity_prepare_ioctl,
    .iterate_devices = verity_iterate_devices,
    .io_hints = verity_io_hints,
};

/* verity_ctr() 주요 파라미터 파싱 */
static int verity_ctr(struct dm_target *ti,
                      unsigned int argc, char **argv)
{
    struct dm_verity *v;
    int r, i;
    unsigned int num;

    v = kzalloc(sizeof(*v), GFP_KERNEL);
    v->ti = ti;

    /* argv[0]: version
     * argv[1]: data_dev
     * argv[2]: hash_dev
     * argv[3]: data_block_size
     * argv[4]: hash_block_size
     * argv[5]: num_data_blocks
     * argv[6]: hash_start_block
     * argv[7]: algorithm
     * argv[8]: root_hash
     * argv[9]: salt (또는 "-") */

    r = dm_get_device(ti, argv[1], FMODE_READ, &v->data_dev);
    r = dm_get_device(ti, argv[2], FMODE_READ, &v->hash_dev);

    /* Crypto API로 해시 알고리즘 할당 */
    v->tfm = crypto_alloc_ahash(argv[7], 0, 0);
    v->digest_size = crypto_ahash_digestsize(v->tfm);

    /* Merkle Tree 레벨 계산 */
    verity_hash_levels(v);

    /* dm_bufio 클라이언트 생성 (해시 블록 캐시) */
    v->bufio = dm_bufio_client_create(v->hash_dev->bdev,
                1 << v->hash_dev_block_bits, 1, 0,
                NULL, NULL, 0);

    /* 검증 워크큐 생성 */
    v->verify_wq = alloc_workqueue("kverityd",
                    WQ_CPU_INTENSIVE | WQ_MEM_RECLAIM, num_online_cpus());

    /* 선택적 파라미터: FEC, check_at_most_once 등 */
    r = verity_parse_opt_args(ti, v, argc - 10, argv + 10);

    ti->private = v;
    return 0;
}
verity_ctr 선택 파라미터설명커널 버전
check_at_most_once블록당 최대 1회 검증4.17+
ignore_corruption검증 실패 시 I/O 허용 (로깅만)4.4+
restart_on_corruption검증 실패 시 시스템 재부팅4.4+
panic_on_corruption검증 실패 시 커널 패닉4.17+
use_fec_from_deviceFEC 디바이스 지정4.4+
fec_rootsRS 패리티 바이트 수4.4+
fec_blocksFEC 보호 블록 수4.4+
fec_startFEC 데이터 시작 블록4.4+
root_hash_sig_key_desc루트 해시 서명 검증(Signature Verification) keyring 키5.4+

dm-verity의 성능 특성을 이해하는 것이 중요합니다. 각 4KB 데이터 블록을 읽을 때마다 SHA-256 해시 1회 + Merkle Tree 레벨 수만큼의 추가 해시 검증이 필요합니다. 단, dm_bufio 캐시 덕분에 상위 레벨 해시 블록은 대부분 캐시 히트되며, 실질적인 오버헤드는 리프 레벨 해시 1회 + 해시 블록 읽기 1-2회 정도입니다. SHA-256의 소프트웨어 처리량(Throughput)은 약 500MB/s이며, SHA-NI 하드웨어 가속 시 2-3GB/s까지 향상됩니다.

Android Verified Boot

Android Verified Boot(AVB)는 dm-verity를 기반으로 시스템 파티션의 무결성을 보장합니다. AVB 2.0(libavb)은 부트로더에서 vbmeta 파티션을 검증하고, vbmeta에 포함된 dm-verity 루트 해시를 커널의 dm-verity target에 전달합니다. 이 체인을 통해 부트로더 → 커널 → 시스템 파티션까지 전체 부팅 경로의 무결성이 보장됩니다.

eFuse / OTP Root of Trust Bootloader vbmeta 서명 검증 vbmeta 파티션 RSA-4096 서명 root_hash, salt, flags 커널 cmdline dm="verity ..." dm-verity target 활성화 system, vendor 파티션 검증 /system (dm-verity) /vendor (dm-verity) /product (dm-verity)
# Android 디바이스에서 dm-verity 상태 확인
$ adb shell dmctl list devices
system-verity     : active
vendor-verity     : active

# vbmeta 구조 확인
$ avbtool info_image --image vbmeta.img
Header Block:           256 bytes
Authentication Block:   576 bytes
Algorithm:              SHA256_RSA4096
Rollback Index:         1
Descriptors:
  Hashtree descriptor:
    dm-verity Version:  1
    Image Size:         3221225472 bytes
    Tree Offset:        3221225472
    Tree Size:          25427968
    Data Block Size:    4096
    Hash Algorithm:     sha256
    FEC num roots:      2
    Root Digest:        abc123...
AVB 부팅 상태설명사용자 표시
GREEN모든 검증 통과, 공식 키정상 부팅
YELLOW검증 통과, 사용자 커스텀 키경고 표시
ORANGE부트로더 언락, 검증 비활성경고 + 5초 대기
RED검증 실패부팅 거부 또는 경고
# Android dm-verity 커맨드라인 (init에서 파싱)
# 부트로더가 커널 cmdline에 삽입하는 dm-verity 파라미터
androidboot.veritymode=enforcing
dm="1 vroot none ro 1,
    0 6291456 verity 1
    PARTUUID=a1b2c3d4 PARTUUID=e5f6g7h8
    4096 4096 786432 786433 sha256
    4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b
    0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
    10 restart_on_corruption ignore_zero_blocks use_fec_from_device
    PARTUUID=i9j0k1l2 fec_roots 2 fec_blocks 786432 fec_start 786432"

# Android init에서 dm-verity 설정 과정
# 1. first_stage_init: 부트 이미지에서 fstab 로드
# 2. fs_mgr: fstab의 verify 옵션 파싱
# 3. fs_mgr_setup_verity(): dm-verity 테이블 생성
# 4. dm_ioctl(DM_TABLE_LOAD): 커널에 테이블 로드
# 5. dm_ioctl(DM_DEV_SUSPEND): 디바이스 활성화
# 6. mount(): 검증된 디바이스 마운트

# Android fstab.device 예시
# system  /system  ext4  ro,barrier=1  wait,verify,first_stage_mount
# vendor  /vendor  ext4  ro,barrier=1  wait,verify,first_stage_mount

# vbmeta에 rollback protection 인덱스 설정
$ avbtool make_vbmeta_image     --output vbmeta.img     --key /path/to/key.pem     --algorithm SHA256_RSA4096     --rollback_index 3     --chain_partition system:1:/path/to/system_key.avbpubkey     --chain_partition vendor:2:/path/to/vendor_key.avbpubkey
Rollback Protection: AVB는 rollback index를 통해 다운그레이드 공격을 방지합니다. eFuse에 기록된 최소 rollback index보다 낮은 버전의 vbmeta는 부트로더가 거부합니다. OTA 업데이트 시 rollback index도 함께 증가시켜야 합니다. 한 번 증가한 eFuse 값은 되돌릴 수 없으므로, 테스트 빌드에서는 --rollback_index 0을 사용하세요.
Android 파티션보호 방식검증 타이밍
bootAVB 서명 검증 (전체 이미지)부트로더
init_bootAVB 서명 검증부트로더
systemdm-verity (Merkle Tree + FEC)first_stage_init
vendordm-verityfirst_stage_init
productdm-verityfirst_stage_init
system_extdm-verityfirst_stage_init
vbmetaRSA-4096 서명부트로더
userdatadm-crypt (FBE) 또는 ICEvold
metadatadm-default-key (메타데이터 암호화)init

Android 12+에서는 File-Based Encryption(FBE)이 기본이며, dm-default-key target으로 메타데이터 암호화를 추가합니다. 기존의 Full-Disk Encryption(FDE, dm-crypt 단일)은 deprecated되었습니다. FBE는 각 사용자의 CE(Credential Encrypted) 키와 DE(Device Encrypted) 키를 분리하여, 잠금 화면에서도 알람이나 전화 수신이 가능합니다.

Forward Error Correction (FEC)

dm-verity FEC는 Reed-Solomon 부호를 사용하여 손상된 블록을 자동 복구합니다. drivers/md/dm-verity-fec.c에 구현되어 있으며, Android에서는 시스템 파티션에 약 0.8%의 FEC 데이터를 추가하여 비트 에러로 인한 검증 실패 시 자동 복구를 시도합니다. FEC는 dm-verity의 선택 기능으로, veritysetup format--fec-device--fec-roots 옵션을 지정하여 활성화합니다.

/* drivers/md/dm-verity-fec.h */
struct dm_verity_fec {
    struct dm_dev *dev;           /* FEC 데이터 디바이스 */
    sector_t start;               /* FEC 데이터 시작 섹터 */
    sector_t blocks;              /* FEC 블록 수 */
    sector_t rounds;              /* RS 인코딩 라운드 수 */
    unsigned int rsn;             /* RS 블록 크기 (255 기본) */
    unsigned int roots;            /* RS 패리티 바이트 수 (2 기본) */
    struct dm_bufio_client *bufio;
    mempool_t rs_pool;            /* RS 컨텍스트 풀 */
};
# FEC가 포함된 dm-verity 설정
$ veritysetup format --fec-device=/dev/sda3 --fec-roots=2 \
    /dev/sda1 /dev/sda2

$ veritysetup open --fec-device=/dev/sda3 --fec-roots=2 \
    /dev/sda1 verified_root /dev/sda2 ROOT_HASH
FEC 오버헤드: --fec-roots=2로 설정하면 RS(255,253)이 적용되어 전체 데이터의 약 0.8%만 추가 공간이 필요합니다. roots 값을 높이면 복구 능력이 증가하지만 공간 오버헤드도 비례하여 증가합니다.

FEC 디코딩 과정을 상세히 살펴보면, dm-verity가 검증 실패를 감지하면 verity_fec_decode()를 호출합니다. 이 함수는 손상된 블록이 포함된 RS 블록을 찾아 decode_rs8()으로 디코딩을 시도합니다. RS(255,253)에서는 최대 1바이트의 에러를 정정할 수 있으며, erasure 위치를 알면 최대 2바이트까지 복구 가능합니다. FEC 복구 성공 시 정정된 데이터가 반환되고, 실패 시 원래의 검증 실패 오류가 전달됩니다.

FEC 파라미터RS 코드패리티공간 오버헤드복구 능력
roots=2RS(255,253)2바이트/블록~0.8%1 에러/블록
roots=4RS(255,251)4바이트/블록~1.6%2 에러/블록
roots=8RS(255,247)8바이트/블록~3.1%4 에러/블록
roots=24RS(255,231)24바이트/블록~9.4%12 에러/블록

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 오버헤드단독 사용
Bitmap (B)비트맵만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해시 알고리즘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에서도 약 5-15%의 오버헤드가 발생합니다. 별도 메타 디바이스(--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-crypt는 블록 레벨 투명 암호화를 제공하는 Device Mapper target입니다. drivers/md/dm-crypt.c에 구현되어 있으며, LUKS(Linux Unified Key Setup) 포맷과 함께 리눅스의 표준 디스크 암호화 솔루션으로 사용됩니다. 모든 읽기/쓰기 I/O가 dm-crypt를 통과하면서 투명하게 암/복호화(Decryption)됩니다.

LUKS2(Linux Unified Key Setup version 2)는 dm-crypt의 표준 온디스크 포맷입니다. LUKS1과 비교하여 JSON 메타데이터, Argon2id PBKDF, 최대 32개 키 슬롯, 큰 헤더 크기(최대 64MB), 인증 암호화(AEAD) 지원 등이 추가되었습니다.

속성LUKS1LUKS2
메타데이터 형식바이너리 헤더JSON + 바이너리
키 슬롯최대 8개최대 32개
PBKDFPBKDF2Argon2id (기본), PBKDF2
헤더 크기고정 2MB가변 (최대 64MB)
AEAD 지원미지원지원 (dm-integrity 결합)
토큰 시스템미지원systemd-cryptenroll 연동
온라인 키 변경미지원지원
무결성 보호미지원HMAC/AEAD 지원
/* LUKS2 온디스크 헤더 구조 (간략화) */
struct luks2_hdr_disk {
    char magic[6];          /* "LUKSº¾" */
    uint16_t version;        /* 2 */
    uint64_t hdr_size;       /* 전체 헤더 크기 */
    uint64_t seqid;          /* 시퀀스 ID (원자적 업데이트) */
    char label[48];          /* 볼륨 라벨 */
    char checksum_alg[32];   /* "sha256" */
    uint8_t salt[64];        /* 헤더 salt */
    char uuid[40];           /* UUID */
    char subsystem[48];      /* 서브시스템 */
    uint64_t hdr_offset;     /* JSON 영역 시작 */
    char _padding[184];
    uint8_t csum[64];        /* 헤더 체크섬 */
    /* 이후: JSON 메타데이터 영역 */
    /* 이후: 키 슬롯 바이너리 영역 */
};

/* LUKS2 JSON 메타데이터 예시 (cryptsetup luksDump) */
/*
{
  "keyslots": {
    "0": {
      "type": "luks2",
      "key_size": 64,
      "af": { "type": "luks1", "stripes": 4000, "hash": "sha256" },
      "kdf": {
        "type": "argon2id",
        "time": 4,
        "memory": 1048576,
        "cpus": 4,
        "salt": "base64..."
      },
      "area": {
        "type": "raw",
        "offset": "32768",
        "size": "258048",
        "encryption": "aes-xts-plain64",
        "key_size": 64
      }
    }
  },
  "segments": {
    "0": {
      "type": "crypt",
      "offset": "16777216",
      "size": "dynamic",
      "iv_tweak": "0",
      "encryption": "aes-xts-plain64",
      "sector_size": 512
    }
  }
}
*/
Argon2id vs PBKDF2: Argon2id는 메모리-하드 함수로, GPU/ASIC 기반 브루트포스 공격에 강합니다. LUKS2 기본 설정은 Argon2id (time=4, memory=1GB, cpus=4)이며, 약 2초의 키 유도 시간이 소요됩니다. 저사양 기기에서는 --pbkdf-memory 옵션으로 메모리를 줄일 수 있습니다.
WRITE 경로 평문 bio crypt_convert() AES-XTS 암호화 암호문 디스크 READ 경로 암호문 디스크 AES-XTS 복호화 crypt_convert() 평문 bio IV (Initialization Vector) 생성 plain64: 섹터 번호를 IV로 사용 ESSIV: 암호화된 섹터 번호 (Watermark 공격 방어)
/* drivers/md/dm-crypt.c */
struct crypt_config {
    struct dm_dev *dev;
    sector_t start;                /* 암호화 시작 섹터 */

    struct crypto_skcipher **tfms;  /* 대칭키 암호 핸들 (CPU별) */
    unsigned int tfms_count;       /* CPU 수만큼 할당 */
    char *cipher_string;           /* "aes-xts-plain64" 등 */

    /* IV 생성 */
    const struct crypt_iv_operations *iv_gen_ops;
    char *iv_mode;                 /* "plain64", "essiv" 등 */
    unsigned int iv_size;

    /* 키 관리 */
    u8 *key;
    unsigned int key_size;
    unsigned int key_parts;
    struct key *key_desc;          /* keyring 키 */

    /* 워크큐 */
    struct workqueue_struct *io_queue;
    struct workqueue_struct *crypt_queue;
    mempool_t page_pool;
    unsigned int num_write_bios;
};
# LUKS2 볼륨 생성 (AES-256-XTS)
$ cryptsetup luksFormat --type luks2 \
    --cipher aes-xts-plain64 \
    --key-size 512 \
    --hash sha256 \
    --pbkdf argon2id \
    /dev/sdb1

# LUKS 볼륨 열기
$ cryptsetup open /dev/sdb1 encrypted_disk

# plain dm-crypt (LUKS 없이)
$ echo "0 $(blockdev --getsz /dev/sdb1) crypt aes-xts-plain64 \
    $(xxd -l 64 -p /dev/urandom | tr -d '\n') \
    0 /dev/sdb1 0" | dmsetup create plain_crypt

암호화 알고리즘

dm-crypt는 커널 Crypto API를 통해 다양한 암호화 알고리즘을 지원합니다. 알고리즘 문자열은 cipher-chainmode-ivmode 형식으로 지정합니다.

알고리즘형식키 크기특징권장 용도
AES-XTSaes-xts-plain64256/512비트표준, 하드웨어 가속일반 디스크 암호화
AES-CBC-ESSIVaes-cbc-essiv:sha256128/256비트레거시, Watermark 방어호환성 필요 시
Adiantumxchacha12,aes-adiantum-plain64256비트AES-NI 없는 환경 최적저사양 ARM 디바이스
AES-GCMaes-gcm-random256비트AEAD (인증 암호화)dm-integrity 결합
Twofish-XTStwofish-xts-plain64256/512비트AES 대안AES 회피 정책
Serpent-XTSserpent-xts-plain64256/512비트보안 마진 최대최고 보안 요구
/* drivers/md/dm-crypt.c - IV 생성 운영 구조체 */
static const struct crypt_iv_operations crypt_iv_plain64_ops = {
    .generator = crypt_iv_plain64_gen,
};

static int crypt_iv_plain64_gen(struct crypt_config *cc,
                                u8 *iv,
                                struct dm_crypt_request *dmreq)
{
    /* IV = 섹터 번호 (64비트 리틀 엔디언) */
    memset(iv, 0, cc->iv_size);
    *(__le64 *)iv = cpu_to_le64(dmreq->iv_sector);
    return 0;
}

/* Adiantum: ChaCha + AES로 구성된 와이드 블록 암호 */
/* AES-NI가 없는 ARMv7/저사양 환경에서 AES-XTS보다 5배 빠름 */
/* Google이 Android Go 디바이스용으로 커널 5.0에 추가 */
Adiantum 선택 기준: AES 하드웨어 가속(AES-NI, ARMv8 CE)이 있으면 AES-XTS가 최선입니다. 가속이 없는 저사양 ARM 디바이스(ARMv7 등)에서는 Adiantum이 소프트웨어 AES-XTS보다 약 5배 빠르며, 보안 수준은 동등합니다.

IV 생성 모드 상세 비교

dm-crypt의 IV(Initialization Vector) 생성 모드는 동일한 평문 블록이 동일한 암호문으로 변환되는 것을 방지합니다. 각 섹터마다 고유한 IV를 생성하여 ECB 모드의 취약점(Vulnerability)을 회피합니다. 주요 IV 생성 모드를 상세히 비교합니다.

IV 모드IV 생성 방법보안 수준성능호환성
plain섹터 번호 (32비트)낮음 (2TB 이상 반복)최고레거시
plain64섹터 번호 (64비트)양호최고권장
plain64be섹터 번호 (64비트 빅 엔디안(Endianness))양호최고특수 HW
essivE(key_hash, 섹터번호)우수 (Watermark 방어)약간 느림LUKS1 기본
benbi섹터 번호 빅 엔디안 (128비트)양호최고하드웨어 AES
eboiv암호화된 블록의 IV우수약간 느림Bitlocker 호환
elephantElephant diffuser + ESSIV우수느림Bitlocker 레거시
random랜덤 IV (AEAD 전용)최고추가 저장 필요AEAD 모드
/* ESSIV (Encrypted Salt-Sector Initialization Vector) */
/* Watermark 공격: 공격자가 섹터 번호를 알면 plain64에서
 * 동일 평문의 섹터를 식별할 수 있음
 * ESSIV는 섹터 번호를 AES(hash(key))로 암호화하여 방어 */

static int crypt_iv_essiv_gen(struct crypt_config *cc,
                               u8 *iv,
                               struct dm_crypt_request *dmreq)
{
    /* IV = AES_ECB(SHA256(master_key), sector_number) */
    memset(iv, 0, cc->iv_size);
    *(__le64 *)iv = cpu_to_le64(dmreq->iv_sector);

    /* ESSIV 전용 cipher로 섹터 번호 암호화 */
    crypto_cipher_encrypt_one(cc->iv_gen_private.essiv.tfm,
                              iv, iv);
    return 0;
}

/* XTS 모드에서는 ESSIV가 불필요 (내장 tweak 메커니즘)
 * AES-XTS: T = AES(key2, sector_number) * alpha^j
 * 각 16바이트 블록마다 고유한 tweak 값 적용 */
XTS vs CBC+ESSIV: XTS 모드는 자체적으로 섹터별 tweak를 생성하므로 별도 IV 모드가 필요 없습니다. CBC+ESSIV는 레거시 호환용이며, 새 설정에서는 항상 AES-XTS를 사용하세요. XTS는 narrow-block tweakable cipher로, 16바이트 블록 단위의 독립적 암호화를 보장합니다.

인라인 암호화 엔진 (ICE) 연동

최신 모바일 SoC(Qualcomm, Samsung, MediaTek)에는 Inline Crypto Engine(ICE)이 내장되어 있어, dm-crypt 없이도 하드웨어 레벨에서 블록 암호화를 수행합니다. Linux 커널은 blk-crypto 프레임워크(block/blk-crypto.c)를 통해 ICE를 활용하며, dm-crypt와 투명하게 대체됩니다.

/* block/blk-crypto.c - 인라인 암호화 프레임워크 */
struct blk_crypto_key {
    struct blk_crypto_config crypto_cfg;
    unsigned int data_unit_size;
    unsigned int size;
    u8 raw[BLK_CRYPTO_MAX_KEY_SIZE];
};

/* bio에 암호화 컨텍스트 연결 */
void bio_crypt_set_ctx(struct bio *bio,
                       const struct blk_crypto_key *key,
                       u64 dun_lo, gfp_t gfp_mask)
{
    struct bio_crypt_ctx *bc;
    bc = mempool_alloc(bio_crypt_ctx_pool, gfp_mask);
    bc->bc_key = key;
    bc->bc_dun[0] = dun_lo;
    bio->bi_crypt_context = bc;
}

/* 하드웨어가 blk-crypto를 지원하지 않으면
 * blk-crypto-fallback이 소프트웨어로 처리 (= dm-crypt과 유사) */
방식암호화 위치CPU 오버헤드지원 플랫폼
dm-crypt소프트웨어 (커널)높음 (AES-NI 시 낮음)모든 플랫폼
ICE (blk-crypto)스토리지 컨트롤러 HW제로Qualcomm, Samsung SoC
SED (OPAL)드라이브 내부 HW제로OPAL 호환 SSD
blk-crypto-fallback소프트웨어 (대체)dm-crypt과 유사ICE 미지원 시

dm-crypt 성능

dm-crypt의 성능은 암호화 연산의 병렬화, 하드웨어 가속, 워크큐 설정에 크게 의존합니다. 커널 5.x 이후 submit_from_crypt_cpus, no_read_workqueue, no_write_workqueue 플래그가 추가되어 성능 최적화가 가능합니다.

WRITE bio crypt_map() dmcrypt_write io_queue (workqueue) crypt_queue (encrypt) no_write_workqueue: 직접 암호화 (컨텍스트 스위칭 회피) 성능 최적화 플래그 no_read_workqueue : 읽기 복호화를 bio 완료 컨텍스트에서 직접 수행 no_write_workqueue: 쓰기 암호화를 submit 컨텍스트에서 직접 수행 submit_from_crypt_cpus: 암호화 완료 CPU에서 직접 bio submit (NUMA 친화) same_cpu_crypt : 암호화/복호화를 동일 CPU에서 수행 (캐시 최적화)
# 성능 최적화된 dm-crypt 설정
$ cryptsetup open --type luks2 /dev/nvme0n1p2 encrypted \
    --perf-no_read_workqueue \
    --perf-no_write_workqueue \
    --perf-submit_from_crypt_cpus

# 런타임에 플래그 변경
$ dmsetup table encrypted --showkeys | \
    sed 's/$/ 3 no_read_workqueue no_write_workqueue submit_from_crypt_cpus/' | \
    dmsetup reload encrypted
$ dmsetup resume encrypted

# fio 벤치마크: 암호화 오버헤드 측정
$ fio --name=randread --ioengine=io_uring --iodepth=64 \
    --rw=randread --bs=4k --direct=1 --numjobs=4 \
    --filename=/dev/mapper/encrypted --runtime=30 \
    --group_reporting
시나리오NVMe 직접dm-crypt 기본dm-crypt 최적화오버헤드
순차 읽기3,500 MB/s2,800 MB/s3,200 MB/s~8%
순차 쓰기3,000 MB/s2,200 MB/s2,700 MB/s~10%
랜덤 4K 읽기800K IOPS600K IOPS720K IOPS~10%
랜덤 4K 쓰기500K IOPS350K IOPS430K IOPS~14%
AES-NI 가속 효과: AES-NI가 활성화된 x86_64 CPU에서 AES-XTS의 단일 코어 처리량은 약 4-5 GB/s입니다. ARMv8 Crypto Extension도 유사한 성능을 제공합니다. cryptsetup benchmark로 시스템의 암호화 처리량을 확인할 수 있습니다.
/* drivers/md/dm-crypt.c - crypt_map() 핵심 */
static int crypt_map(struct dm_target *ti, struct bio *bio)
{
    struct crypt_config *cc = ti->private;
    struct dm_crypt_io *io;

    /* FLUSH/PREFLUSH 바이패스 */
    if (unlikely(bio->bi_opf & REQ_PREFLUSH)) {
        bio_set_dev(bio, cc->dev->bdev);
        return DM_MAPIO_REMAPPED;
    }

    io = dm_per_bio_data(bio, cc->per_bio_data_size);
    crypt_io_init(io, cc, bio, bio->bi_iter.bi_sector);

    if (bio_data_dir(bio) == READ) {
        if (cc->on_disk_tag_size) {
            /* AEAD 모드: 태그도 함께 읽기 */
            io->ctx.bio_in = bio;
            io->ctx.iter_in = bio->bi_iter;
        }
        crypt_read(io);
    } else {
        crypt_write(io);
    }

    return DM_MAPIO_SUBMITTED;
}

/* 암호화 변환 - CPU별 tfm 사용으로 락 경합 방지 */
static int crypt_convert(struct crypt_config *cc,
                         struct convert_context *ctx,
                         bool aead_recheck,
                         bool reset_pending)
{
    unsigned int tag_offset = 0;
    sector_t sector = ctx->cc_sector;
    int r;

    /* CPU별 암호 핸들 선택 (lock-free) */
    struct crypto_skcipher *tfm =
        cc->tfms[smp_processor_id() % cc->tfms_count];

    while (ctx->iter_in.bi_size) {
        /* IV 생성 */
        cc->iv_gen_ops->generator(cc, org_iv, &dmreq->iv_sector);

        /* skcipher 비동기 요청 */
        skcipher_request_set_crypt(req, &sg_in, &sg_out,
                                   cc->sector_size, org_iv);

        if (bio_data_dir(ctx->bio_in) == WRITE)
            r = crypto_skcipher_encrypt(req);
        else
            r = crypto_skcipher_decrypt(req);

        sector++;
    }
    return 0;
}
CPU별 tfm 할당: dm-crypt는 num_online_cpus()만큼의 crypto_skcipher 핸들을 할당합니다. 각 CPU가 자신의 핸들을 사용하므로 락 경합(Contention) 없이 병렬 암호화가 가능합니다. 이 설계는 멀티코어 시스템에서 선형적인 성능 확장을 제공합니다. 예를 들어 8코어 시스템에서 AES-XTS의 총 처리량은 약 20-30GB/s에 달합니다.

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~15%
AES-XTS + HMACAES-XTSHMAC-SHA25632B~25%
ChaCha20-Poly1305ChaCha20Poly130516B~12%
AES-XTS 단독AES-XTS없음0B~8%
AEAD 모드 주의사항: AEAD 모드에서는 dm-integrity가 no-journal 모드로 동작합니다. 정전 시 데이터+태그 불일치로 해당 섹터가 읽기 불가능해질 수 있습니다. UPS 또는 배터리 백업이 없는 환경에서는 저널 모드가 권장됩니다.

dm-flakey / dm-dust

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

/* 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를 사용하여 정전 시나리오를 시뮬레이션합니다.

dm-user / dm-verity-loadpin

dm-user는 사용자 공간에서 Device Mapper target을 구현할 수 있게 하는 메커니즘입니다. ChromeOS와 Android에서 보안 부팅 체인을 강화하는 데 사용됩니다. dm-verity-loadpin은 커널 모듈(Kernel Module)과 펌웨어(Firmware) 로딩을 dm-verity로 검증된 파티션으로 제한합니다.

/* security/loadpin/loadpin.c */
/* LoadPin: 모든 커널 모듈/펌웨어가
 * 동일한 검증된 파일시스템에서만 로드되도록 강제 */

static int loadpin_read_file(struct file *file,
                            enum kernel_read_file_id id,
                            bool contents)
{
    struct super_block *load_root = READ_ONCE(pinned_root);

    if (!load_root) {
        /* 첫 번째 로드: 이 파일시스템을 "pin" */
        pinned_root = file->f_path.mnt->mnt_sb;
        return 0;
    }

    /* 이후 모든 로드는 pinned root와 동일한 fs에서만 허용 */
    if (file->f_path.mnt->mnt_sb != load_root) {
        pr_info("LoadPin: denied loading from different fs\n");
        return -EPERM;
    }
    return 0;
}

/* dm-verity + LoadPin = 검증된 파티션에서만 커널 모듈 로드 */
# ChromeOS dm-verity + LoadPin 부팅 체인
# 1. 부트로더가 커널 서명 검증
# 2. 커널이 rootfs에 dm-verity 활성화
# 3. LoadPin이 첫 번째 모듈 로드 경로를 기록
# 4. 이후 모든 모듈/펌웨어는 동일 경로에서만 로드 허용

# Android: dm-verity + dm-user (snapuserd)
# snapuserd는 Virtual A/B OTA 업데이트에서
# copy-on-write 스냅샷을 사용자 공간에서 처리
$ ps aux | grep snapuserd
root  1234  0.5  0.2  snapuserd /dev/dm-user/system_cow
Android Virtual A/B: Android 12+에서는 dm-usersnapuserd를 사용하여 OTA 업데이트 중 copy-on-write 스냅샷을 관리합니다. dm-verity 위에 dm-snapshot(또는 dm-user 기반 COW)을 쌓아 업데이트 중에도 시스템 무결성을 유지합니다.

Device Mapper 타겟 개발

커스텀 Device Mapper target을 개발하려면 struct target_type을 구현하고 dm_register_target()으로 등록합니다. 최소한 ctr(생성자), dtr(소멸자), map(I/O 매핑) 콜백이 필요합니다.

/* 예제: 최소 dm target 구현 */
#include <linux/device-mapper.h>

struct my_target_data {
    struct dm_dev *dev;
    sector_t start;
    atomic64_t io_count;
};

/* 생성자: dmsetup create 시 호출 */
static int my_ctr(struct dm_target *ti,
                  unsigned int argc, char **argv)
{
    struct my_target_data *mtd;
    int r;

    if (argc != 2) {
        ti->error = "requires 2 arguments: dev_path offset";
        return -EINVAL;
    }

    mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
    if (!mtd)
        return -ENOMEM;

    r = dm_get_device(ti, argv[0], dm_table_get_mode(ti->table),
                      &mtd->dev);
    if (r) {
        kfree(mtd);
        return r;
    }

    if (kstrtoull(argv[1], 10, &mtd->start)) {
        dm_put_device(ti, mtd->dev);
        kfree(mtd);
        return -EINVAL;
    }

    atomic64_set(&mtd->io_count, 0);
    ti->private = mtd;
    return 0;
}

/* 소멸자 */
static void my_dtr(struct dm_target *ti)
{
    struct my_target_data *mtd = ti->private;
    dm_put_device(ti, mtd->dev);
    kfree(mtd);
}

/* I/O 매핑: 모든 bio에 대해 호출 */
static int my_map(struct dm_target *ti, struct bio *bio)
{
    struct my_target_data *mtd = ti->private;

    atomic64_inc(&mtd->io_count);
    bio_set_dev(bio, mtd->dev->bdev);
    bio->bi_iter.bi_sector = mtd->start +
        dm_target_offset(ti, bio->bi_iter.bi_sector);

    return DM_MAPIO_REMAPPED;
}

/* 상태 보고 */
static void my_status(struct dm_target *ti,
                      status_type_t type,
                      unsigned int status_flags,
                      char *result, unsigned int maxlen)
{
    struct my_target_data *mtd = ti->private;

    switch (type) {
    case STATUSTYPE_INFO:
        snprintf(result, maxlen, "io_count=%lld",
                 atomic64_read(&mtd->io_count));
        break;
    case STATUSTYPE_TABLE:
        snprintf(result, maxlen, "%s %llu",
                 mtd->dev->name, mtd->start);
        break;
    }
}

static struct target_type my_target = {
    .name    = "my_target",
    .version = {1, 0, 0},
    .module  = THIS_MODULE,
    .ctr     = my_ctr,
    .dtr     = my_dtr,
    .map     = my_map,
    .status  = my_status,
};

static int __init my_init(void)
{
    return dm_register_target(&my_target);
}

static void __exit my_exit(void)
{
    dm_unregister_target(&my_target);
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
콜백호출 시점반환값필수 여부
ctr테이블 로드 시0 성공, 음수 오류필수
dtr디바이스 제거 시없음필수
map매 bio마다DM_MAPIO_*필수
end_iobio 완료 시DM_ENDIO_*선택
statusdmsetup status/table없음선택
messagedmsetup message0 또는 오류선택
iterate_devices하위 디바이스 탐색0 또는 오류선택
io_hintsI/O 힌트 설정없음선택
map() 반환값: DM_MAPIO_SUBMITTED: target이 bio를 완전히 처리 (비동기 완료 예정), DM_MAPIO_REMAPPED: bio가 하위 디바이스로 redirect됨 (DM 코어가 submit), DM_MAPIO_REQUEUE: bio를 재큐 (리소스 부족 시), DM_MAPIO_KILL: bio를 에러로 완료
/* Makefile for out-of-tree DM target module */
obj-m := dm-mytarget.o

KDIR := /lib/modules/$(shell uname -r)/build

all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean

# 빌드 및 로드
$ make
$ sudo insmod dm-mytarget.ko
$ sudo dmsetup create mydev --table "0 2097152 my_target /dev/sdb1 0"

# 상태 확인
$ sudo dmsetup status mydev
0 2097152 my_target io_count=42

# 제거
$ sudo dmsetup remove mydev
$ sudo rmmod dm-mytarget

고급 target 개발 시 고려해야 할 사항들입니다:

고려사항설명관련 콜백/플래그
bio 분할target 경계에서 bio가 분할될 수 있음max_io_len()
flush 지원REQ_PREFLUSH/REQ_FUA 처리num_flush_bios
discard 지원TRIM/discard 명령 전달num_discard_bios
suspend/resume테이블 교체 시 I/O 일시 중지presuspend(), resume()
에러 처리하위 디바이스 에러 전파end_io()
메모리 할당map() 컨텍스트에서 GFP_NOIO 사용mempool 사전 할당
통계/모니터링I/O 카운터, 지연 시간 측정status(), message()

성능 최적화

Device Mapper target들의 성능을 최적화하려면 I/O 스케줄링, prefetch, 병렬 처리, 메모리 할당 전략을 종합적으로 고려해야 합니다. 특히 dm-verity와 dm-crypt는 각각 해시 계산과 암호화 연산이 병목(Bottleneck)이 되므로, 하드웨어 가속과 병렬화가 핵심입니다.

dm-verity 최적화 1. 해시 블록 Prefetch (readahead) 2. 검증된 블록 캐시 (validated_blocks) 3. 워크큐 CPU 바인딩 4. SHA-256 하드웨어 가속 (SHA-NI) 5. check_at_most_once 플래그 dm-crypt 최적화 1. AES-NI / ARMv8 CE 가속 2. no_read/write_workqueue 3. submit_from_crypt_cpus 4. 대용량 bio 처리 (max_write_same) 5. NUMA 친화 메모리 할당 dm-integrity 최적화 1. Bitmap 모드 (저널 대비 2-3x 빠름) 2. 별도 메타 디바이스 분리 3. journal-watermark 최적화 4. interleave-sectors 조정 5. buffer_sectors 튜닝 공통 최적화 1. I/O 스케줄러 선택 (none/mq-deadline) 2. readahead 크기 조정 3. 블록 크기 4096 (4K 정렬) 4. NVMe 멀티큐 활용 5. DM 스택 깊이 최소화
# dm-verity prefetch 및 캐시 최적화
# check_at_most_once: 각 블록을 최대 1회만 검증 (메모리 사용 증가)
$ echo "0 2097152 verity 1 /dev/sda1 /dev/sda2 4096 4096 \
    262144 1 sha256 ROOT_HASH SALT \
    2 check_at_most_once use_fec_from_device /dev/sda3" | \
    dmsetup create verified_opt

# dm-crypt I/O 스케줄러 최적화
$ echo none > /sys/block/dm-0/queue/scheduler
$ echo 256 > /sys/block/dm-0/queue/read_ahead_kb

# dm-integrity bitmap 모드 (성능 우선)
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --integrity-bitmap-mode \
    --bitmap-sectors-per-bit 65536 \
    --bitmap-flush-time 5000

# 암호화 성능 벤치마크
$ cryptsetup benchmark
# Tests are approximate using memory only (no storage IO).
# Algorithm |       Key |      Encryption |      Decryption
    aes-xts        256b      4512.5 MiB/s      4487.3 MiB/s
    aes-xts        512b      3891.2 MiB/s      3867.8 MiB/s
   serpent-xts     256b       618.3 MiB/s       612.7 MiB/s
   twofish-xts     256b       504.1 MiB/s       510.3 MiB/s
    aes-cbc        256b      1356.8 MiB/s      4231.4 MiB/s
NVMe + dm-crypt 최적 설정: no_read_workqueue + no_write_workqueue + submit_from_crypt_cpus를 모두 활성화하면 NVMe SSD에서 암호화 오버헤드를 10% 미만으로 줄일 수 있습니다. same_cpu_crypt는 NUMA 시스템에서 캐시 지역성을 개선합니다.

보안 고려사항

dm-crypt/dm-verity/dm-integrity의 보안은 키 관리, 알고리즘 선택, 부팅 체인 무결성에 의존합니다. 커널의 keyring 서브시스템, TPM, FIPS 모드를 활용하여 보안을 강화할 수 있습니다.

TPM 2.0 Sealed Key 사용자 비밀번호 Argon2id PBKDF FIDO2 / YubiKey HMAC-Secret 커널 Keyring user/logon 키 LUKS2 Key Slots 최대 32개 키 슬롯 Volume Master Key dm-crypt (crypto_skcipher)
# TPM 2.0 Sealed Key로 LUKS 잠금 해제
$ systemd-cryptenroll --tpm2-device=auto /dev/sdb1
$ cryptsetup open --token-only /dev/sdb1 encrypted

# FIDO2 하드웨어 토큰 등록
$ systemd-cryptenroll --fido2-device=auto /dev/sdb1

# 커널 keyring 연동
$ keyctl add logon dmcrypt:my_key 32 @s < /path/to/key
$ echo "0 $(blockdev --getsz /dev/sdb1) crypt aes-xts-plain64 \
    :64:logon:dmcrypt:my_key 0 /dev/sdb1 0" | dmsetup create enc

# FIPS 모드 확인
$ cat /proc/sys/crypto/fips_enabled
1
# FIPS 모드에서는 승인된 알고리즘만 사용 가능
# AES-XTS, AES-CBC, SHA-256, HMAC-SHA256 등
보안 기능설명설정 방법
TPM Sealed KeyPCR 값에 바인딩된 암호키systemd-cryptenroll --tpm2-device
FIDO2 Token하드웨어 인증 토큰systemd-cryptenroll --fido2-device
Argon2id KDF메모리-하드 키 유도LUKS2 기본, --pbkdf argon2id
Key Slot 격리(Isolation)다중 인증 방식 지원LUKS2 최대 32개 슬롯
Suspend 보호RAM에서 키 삭제cryptsetup luksSuspend
FIPS 모드승인 알고리즘만 허용fips=1 부팅 파라미터
Cold Boot 공격 방어: dm-crypt 키는 커널 메모리에 평문으로 존재합니다. 물리적 접근이 가능한 공격자는 냉동 메모리 공격(Cold Boot Attack)으로 키를 추출할 수 있습니다. cryptsetup luksSuspend로 키를 메모리에서 삭제하거나, CONFIG_INIT_ON_FREE_DEFAULT_ON으로 해제 메모리를 0으로 초기화하세요.
# TPM2 PCR 정책으로 LUKS 키 봉인
# PCR7 = Secure Boot 상태, PCR4 = 부트 관리자
$ systemd-cryptenroll --tpm2-device=auto     --tpm2-pcrs=4+7     /dev/sdb1

# TPM 봉인 키 상태 확인
$ systemd-cryptenroll /dev/sdb1
SLOT TYPE
0    password
1    tpm2 (pcrs: 4+7, device: /dev/tpmrm0)

# FIDO2 YubiKey로 잠금 해제
$ systemd-cryptenroll --fido2-device=auto     --fido2-with-client-pin=yes     --fido2-with-user-presence=yes     /dev/sdb1

# 복구 키 생성 (비상 잠금 해제용)
$ systemd-cryptenroll --recovery-key /dev/sdb1
# 출력: mmmm-mmmm-mmmm-mmmm-mmmm-mmmm-mmmm-mmmm (base58 복구 키)

luksSuspendluksResume을 활용한 키 보호 전략도 중요합니다. 시스템이 절전 모드(suspend to RAM)에 진입할 때 luksSuspend로 dm-crypt의 마스터 키를 커널 메모리에서 삭제하면, Cold Boot 공격으로부터 키를 보호할 수 있습니다. 복귀 시 luksResume으로 비밀번호를 재입력받아 키를 복원합니다.

# 절전 전 키 삭제
$ cryptsetup luksSuspend encrypted

# 복귀 후 키 복원
$ cryptsetup luksResume encrypted

# systemd suspend hook 설정
# /etc/systemd/system/cryptsetup-suspend@.service
[Unit]
Description=Suspend dm-crypt device
Before=sleep.target

[Service]
Type=oneshot
ExecStart=/usr/bin/cryptsetup luksSuspend %i
ExecStartPost=/usr/bin/systemctl suspend

[Install]
WantedBy=sleep.target
보안 강화 커널 설정: CONFIG_INIT_ON_FREE_DEFAULT_ON=y로 해제 메모리를 자동 초기화하고, CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y로 할당 메모리도 초기화하면 메모리 잔류 데이터를 통한 키 유출 위험을 줄일 수 있습니다. 또한 CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y로 루트 해시의 커널 키링(Keyring) 서명 검증을 활성화할 수 있습니다.
공격 벡터위협대응 방안
Cold BootRAM에서 키 추출luksSuspend, INIT_ON_FREE
Evil Maid부트로더/initramfs 변조Secure Boot + TPM PCR
DMA 공격Thunderbolt/PCIe DMA로 메모리 접근IOMMU, Thunderbolt 보안 레벨
브루트포스비밀번호 반복 시도Argon2id (메모리-하드 KDF)
키 로깅비밀번호 입력 가로채기FIDO2/TPM (비밀번호 불필요)
Wear LevelingSSD에서 삭제된 키 복구SED (Self-Encrypting Drive)
Side Channel타이밍/전력 분석으로 키 추론constant-time crypto, AES-NI
/* 커널 dm-verity 루트 해시 서명 검증 */
/* CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y 필요 */

/* 서명 검증 흐름:
 * 1. verity_ctr()에서 root_hash_sig_key_desc 옵션 파싱
 * 2. 커널 keyring에서 서명 키 조회 (.builtin_trusted_keys)
 * 3. PKCS#7 서명 검증 (verify_pkcs7_signature())
 * 4. 서명이 유효하면 dm-verity 활성화 허용 */

static int verity_verify_root_hash(struct dm_verity *v,
                                    const void *root_hash,
                                    size_t root_hash_len,
                                    const void *sig,
                                    size_t sig_len)
{
    struct key *key;
    int r;

    /* .dm_verity_keys 또는 .builtin_trusted_keys 키링 검색 */
    key = keyring_search(
        make_key_ref(VERIFY_USE_SECONDARY_KEYRING, 1),
        &key_type_asymmetric, v->root_hash_sig_key_desc);

    r = verify_pkcs7_signature(root_hash, root_hash_len,
                               sig, sig_len,
                               VERIFY_USE_SECONDARY_KEYRING,
                               VERIFYING_MODULE_SIGNATURE,
                               NULL, NULL);
    return r;
}

실전 활용

dm-verity, dm-integrity, dm-crypt는 서버 디스크 암호화, 임베디드 검증 부팅, 컨테이너 이미지 보호, CI/CD 파이프라인(Pipeline) 등 다양한 실전 시나리오에서 활용됩니다.

서버 디스크 암호화 LUKS2 + AES-XTS-256 TPM sealed key + Clevis + AEAD (선택) 무결성 자동 잠금 해제 (Clevis/Tang) 임베디드 검증 부팅 dm-verity (system/vendor) dm-crypt (userdata) FEC 데이터 복구 eFuse Root of Trust 컨테이너 / CI/CD dm-verity 이미지 서명 SquashFS + verity dm-crypt 빌드 아티팩트 Keylime 원격 증명 Device Mapper Framework (dm.c) 블록 디바이스 (NVMe / eMMC / SATA)
## 시나리오 1: 서버 전체 디스크 암호화 (Clevis + Tang)

# 1. LUKS2 포맷
$ cryptsetup luksFormat --type luks2 \
    --cipher aes-xts-plain64 --key-size 512 \
    --pbkdf argon2id /dev/nvme0n1p3

# 2. Tang 서버 바인딩 (네트워크 기반 자동 잠금 해제)
$ clevis luks bind -d /dev/nvme0n1p3 tang \
    '{"url":"http://tang.internal:7500"}'

# 3. 부팅 시 자동 잠금 해제 (initramfs)
$ dracut -f --regenerate-all  # clevis dracut 모듈 포함

## 시나리오 2: 임베디드 검증 부팅 이미지 빌드

# 1. 루트 파일시스템 이미지 생성
$ mksquashfs rootfs/ rootfs.img -comp zstd

# 2. dm-verity 해시 트리 생성
$ veritysetup format rootfs.img hash.img > verity_info.txt
$ ROOT_HASH=$(grep "Root hash" verity_info.txt | awk '{print $3}')

# 3. FEC 데이터 생성
$ veritysetup format --fec-device=fec.img --fec-roots=2 \
    rootfs.img hash.img

# 4. 커널 cmdline에 dm-verity 파라미터 추가
$ echo "dm=\"1 vroot none ro 1,0 $(stat -c%s rootfs.img | \
    awk '{print $1/512}') verity 1 PARTUUID=xxx PARTUUID=yyy \
    4096 4096 $(stat -c%s rootfs.img | awk '{print $1/4096}') \
    1 sha256 $ROOT_HASH -\"" >> cmdline.txt

## 시나리오 3: 컨테이너 이미지 + dm-verity

# composefs / ostree + dm-verity
$ mkcomposefs --digest-store=objects/ rootfs/ image.cfs
# composefs는 erofs + dm-verity를 사용하여
# 컨테이너 이미지의 무결성을 검증
시나리오dm target키 관리부팅 자동화
서버 FDEdm-crypt (LUKS2)TPM / Tang / PassphraseClevis + dracut
Android 기기dm-verity + dm-crypteFuse + KEKAVB + init
ChromeOSdm-verity + LoadPinTPM + 서명 키cros_init
IoT 디바이스dm-verity공개키 서명U-Boot + FIT
컨테이너dm-verity (composefs)Sigstore / Cosigncontainerd
systemd-cryptenroll: 최신 systemd(v248+)는 systemd-cryptenroll을 통해 TPM2, FIDO2, PKCS#11 토큰, 복구 키를 LUKS2 키 슬롯에 간편하게 등록할 수 있습니다. 기존의 crypttab + keyfile 방식보다 보안과 편의성 모두 향상됩니다.

LUKS2 키 관리 운영

# LUKS2 헤더 정보 확인
$ cryptsetup luksDump /dev/sdb1
LUKS header information
Version:        2
Epoch:          5
Metadata area:  16384 [bytes]
Keyslots area:  16744448 [bytes]
UUID:           a1b2c3d4-e5f6-7890-abcd-ef1234567890
Label:          encrypted-data
Subsystem:      (no subsystem)
Flags:          (no flags)

Keyslots:
  0: luks2
    Key:        512 bits
    Priority:   normal
    Cipher:     aes-xts-plain64
    Cipher key: 512 bits
    PBKDF:      argon2id
    Time cost:  4
    Memory:     1048576
    Threads:    4

  1: luks2 (tpm2)
    Key:        512 bits
    Priority:   prefer
    Cipher:     aes-xts-plain64

Tokens:
  0: systemd-tpm2
    tpm2-pcrs: 4+7
    tpm2-bank: sha256

Data segments:
  0: crypt
    offset:  16777216 [bytes]
    length:  (whole device)
    cipher:  aes-xts-plain64
    sector:  4096 [bytes]

# 키 슬롯 추가 (새 비밀번호)
$ cryptsetup luksAddKey /dev/sdb1

# 키 슬롯 제거
$ cryptsetup luksKillSlot /dev/sdb1 2

# 온라인 마스터 키 변경 (LUKS2 전용)
$ cryptsetup reencrypt --init-only /dev/sdb1
$ cryptsetup reencrypt --resume-only /dev/sdb1

# 알고리즘 변경 (재암호화)
$ cryptsetup reencrypt /dev/sdb1     --cipher aes-xts-plain64 --key-size 512

# LUKS 볼륨 크기 확장
$ cryptsetup resize encrypted
$ resize2fs /dev/mapper/encrypted
LUKS 헤더 백업 필수: LUKS 헤더가 손상되면 전체 볼륨의 데이터를 잃게 됩니다. 반드시 cryptsetup luksHeaderBackup으로 헤더를 별도 저장소에 백업하세요. LUKS2 헤더는 이중화(primary + secondary)되어 있어 단일 손상에는 자동 복구되지만, 물리적 손상이나 실수로 인한 덮어쓰기에는 무력합니다.
## 시나리오 4: Kubernetes 시크릿 볼륨 암호화

# CSI 드라이버 + dm-crypt으로 PVC 암호화
# 1. StorageClass에 암호화 파라미터 정의
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: encrypted-ssd
provisioner: csi.example.com
parameters:
  encryption: "true"
  cipher: "aes-xts-plain64"
  keySize: "512"
  pbkdf: "argon2id"

# 2. 노드에서 PVC가 마운트될 때 자동 LUKS 포맷/열기
# CSI NodeStageVolume에서:
# - cryptsetup luksFormat /dev/xvdf
# - cryptsetup open /dev/xvdf pvc-xxx
# - mkfs.ext4 /dev/mapper/pvc-xxx
# - mount /dev/mapper/pvc-xxx /var/lib/kubelet/pods/...
## 시나리오 5: dm-verity + SquashFS 컨테이너 이미지

# 1. 컨테이너 rootfs를 SquashFS 이미지로 압축
$ mksquashfs container_rootfs/ image.squashfs -comp zstd -Xcompression-level 19

# 2. dm-verity 해시 트리 생성
$ veritysetup format image.squashfs hash.img     --hash sha256 --data-block-size 4096
Root hash: abcdef...

# 3. 이미지 서명 (cosign 또는 GPG)
$ cosign sign --key cosign.key docker.io/myapp:latest

# 4. 런타임에 검증된 마운트
$ losetup /dev/loop0 image.squashfs
$ losetup /dev/loop1 hash.img
$ veritysetup open /dev/loop0 verified_container /dev/loop1 abcdef...
$ mount -t squashfs /dev/mapper/verified_container /mnt/container
## 시나리오 6: dm-integrity로 서버 데이터 무결성 보호

# 데이터베이스 서버: HMAC-SHA256 무결성 + AES-XTS 암호화
$ cryptsetup luksFormat --type luks2     --cipher aes-xts-plain64     --integrity hmac-sha256     --key-size 512     --sector-size 4096     /dev/nvme0n1p3

# 성능 최적화 옵션으로 열기
$ cryptsetup open /dev/nvme0n1p3 db_encrypted     --perf-no_read_workqueue     --perf-no_write_workqueue     --perf-submit_from_crypt_cpus

# XFS 파일시스템으로 데이터베이스 볼륨 구성
$ mkfs.xfs -f -s size=4096 /dev/mapper/db_encrypted
$ mount -o noatime,nodiratime /dev/mapper/db_encrypted /var/lib/postgresql
서버 FDE 스택 ext4 / XFS dm-crypt (LUKS2) dm-integrity (AEAD) NVMe SSD Android 스택 ext4 (system) / f2fs (data) dm-verity dm-crypt ICE (Inline Crypto) UFS / eMMC IoT 스택 SquashFS (RO) dm-verity + FEC MTD / eMMC 커널 설정 (Kconfig) CONFIG_DM_VERITY=y # dm-verity 지원 CONFIG_DM_VERITY_FEC=y # Forward Error Correction CONFIG_DM_CRYPT=y # dm-crypt 암호화 CONFIG_DM_INTEGRITY=y # dm-integrity 무결성 CONFIG_DM_FLAKEY=m # 테스트용 (모듈)

dm-crypt 디버깅(Debugging) 및 모니터링

# dm-crypt I/O 통계 확인
$ dmsetup status encrypted
0 2097152 crypt aes-xts-plain64 in_flight:12 ...

# 블록 레이어 I/O 통계
$ cat /sys/block/dm-0/stat
  28547  0  456752  1230  15823  0  253168  890  0  1420  2120

# dm-crypt 워크큐 모니터링
$ cat /proc/sys/kernel/threads-max
$ ps aux | grep kcryptd
root     123  0.5  0.0  [kcryptd]
root     124  0.5  0.0  [kcryptd_io]

# crypto API 알고리즘 확인
$ cat /proc/crypto | grep -A5 "name.*xts(aes)"
name         : xts(aes)
driver       : xts-aes-aesni
module       : aesni_intel
priority     : 401
refcnt       : 3
type         : skcipher

# AES-NI 활성화 확인
$ grep aes /proc/cpuinfo | head -1
flags : ... aes ...

# perf를 이용한 암호화 성능 프로파일링
$ perf top -p $(pgrep kcryptd) -g
# aesni_xts_encrypt/decrypt 함수가 상위에 표시되면 정상

# blktrace로 dm-crypt I/O 지연 분석
$ blktrace -d /dev/dm-0 -o trace &
$ fio --name=test --filename=/dev/mapper/encrypted --rw=randread     --bs=4k --iodepth=32 --numjobs=1 --runtime=10
$ kill %1
$ blkparse -i trace -o analysis.txt
$ btt -i trace.blktrace.0 -o btt_result
문제 상황증상해결 방법
AES-NI 미활성CPU 사용률 높음, 처리량 낮음modprobe aesni_intel, BIOS 확인
워크큐 병목kcryptd CPU 100%no_read/write_workqueue 활성화
NUMA 비효율원격 메모리 접근 증가submit_from_crypt_cpus 활성화
저널 병목 (integrity)쓰기 대기 시간(Latency) 증가bitmap 모드 전환, 저널 크기 증가
해시 캐시 미스 (verity)해시 블록 읽기 I/O 급증check_at_most_once, readahead 증가
키 분실볼륨 접근 불가복구 키, LUKS 헤더 백업
# LUKS 헤더 백업 (필수!)
$ cryptsetup luksHeaderBackup /dev/sdb1     --header-backup-file /backup/sdb1-luks-header.img

# LUKS 헤더 복원
$ cryptsetup luksHeaderRestore /dev/sdb1     --header-backup-file /backup/sdb1-luks-header.img

# dm-verity 에러 로그 확인
$ dmesg | grep -i verity
[  12.345678] device-mapper: verity: sha256 using implementation "sha256-avx2"
[  12.345679] device-mapper: verity: 262144 data blocks verified

# dm-integrity 에러 로그
$ dmesg | grep -i integrity
[  15.678901] device-mapper: integrity: initialized, tag size 32
[  15.678902] device-mapper: integrity: journal sectors 131072

# ftrace로 dm 함수 추적
$ echo 1 > /sys/kernel/debug/tracing/events/block/block_bio_remap/enable
$ echo "dm*" > /sys/kernel/debug/tracing/set_ftrace_filter
$ echo function_graph > /sys/kernel/debug/tracing/current_tracer
$ cat /sys/kernel/debug/tracing/trace_pipe | head -50

커널 설정 (Kconfig) 상세

설정설명권장값의존성
CONFIG_BLK_DEV_DMDevice Mapper 코어yBLK_DEV
CONFIG_DM_CRYPTdm-crypt 암호화yBLK_DEV_DM, CRYPTO
CONFIG_DM_VERITYdm-verity 검증yBLK_DEV_DM
CONFIG_DM_VERITY_FECFEC 지원yDM_VERITY
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG루트 해시 서명 검증y (보안)DM_VERITY, SYSTEM_DATA_VERIFICATION
CONFIG_DM_INTEGRITYdm-integrity 무결성yBLK_DEV_DM, BLK_DEV_INTEGRITY
CONFIG_DM_FLAKEYdm-flakey (테스트)mBLK_DEV_DM
CONFIG_DM_DUSTdm-dust (테스트)mBLK_DEV_DM
CONFIG_CRYPTO_AES_NI_INTELAES-NI 가속y (x86)CRYPTO_AES, X86
CONFIG_CRYPTO_XTSXTS 모드yCRYPTO
CONFIG_CRYPTO_ADIANTUMAdiantum 알고리즘y (ARM)CRYPTO
CONFIG_CRYPTO_USER_API_AEAD사용자 AEAD APIyCRYPTO_AEAD

dm-verity vs fs-verity 비교

커널은 dm-verity 외에도 fs-verity(fs/verity/)를 제공합니다. 두 가지 모두 Merkle Tree 기반 무결성 검증이지만, 동작 레벨이 다릅니다.

속성dm-verityfs-verity
동작 레벨블록 디바이스파일시스템 (per-file)
검증 범위전체 파티션개별 파일
설정 단위파티션파일 (inode)
쓰기 지원읽기 전용읽기 전용 (immutable)
커널 소스drivers/md/fs/verity/
사용 도구veritysetupfsverity (util-linux)
지원 FS무관 (블록 레벨)ext4, f2fs, btrfs
주요 용도Verified Boot, OS 이미지APK, 커널 모듈 개별 검증
해시 저장별도 파티션파일 메타데이터 (xattr)
# fs-verity로 개별 파일 검증 설정
$ fsverity enable /mnt/ext4/important_binary
$ fsverity measure /mnt/ext4/important_binary
sha256:abcdef1234567890... /mnt/ext4/important_binary

# Android에서 APK 검증에 fs-verity 사용 (Android 11+)
# dm-verity는 system 파티션 전체 검증
# fs-verity는 개별 APK 파일 검증

dm-bufio 캐시 아키텍처

dm-verity와 dm-integrity 모두 해시/태그 블록 읽기에 dm_bufio 캐시를 사용합니다. drivers/md/dm-bufio.c에 구현된 dm-bufio는 블록 단위 캐시로, LRU 정책으로 관리되며, 메모리 압박 시 shrinker를 통해 회수됩니다. dm-verity 성능은 해시 블록의 캐시 적중률에 크게 의존합니다.

/* drivers/md/dm-bufio.c - 캐시 클라이언트 생성 */
struct dm_bufio_client *dm_bufio_client_create(
    struct block_device *bdev,
    unsigned int block_size,
    unsigned int reserved_buffers,
    unsigned int aux_size,
    void (*alloc_callback)(struct dm_buffer *),
    void (*write_callback)(struct dm_buffer *),
    unsigned int flags);

/* 캐시 조회/읽기 */
struct dm_buffer *dm_bufio_read(
    struct dm_bufio_client *c,
    sector_t block,
    void **data);
/* 캐시 히트 시 즉시 반환, 미스 시 디스크에서 읽기 */

/* 비동기 prefetch (dm-verity에서 사용) */
void dm_bufio_prefetch(
    struct dm_bufio_client *c,
    sector_t block,
    unsigned int n_blocks);

/* 메모리 압박 시 캐시 축소 (shrinker) */
static unsigned long dm_bufio_shrink_scan(
    struct shrinker *shrink,
    struct shrink_control *sc)
{
    /* LRU 리스트에서 가장 오래된 버퍼부터 해제 */
    return __scan(c, sc->nr_to_scan, sc->gfp_mask);
}
dm-bufio 캐시 모니터링: /sys/module/dm_bufio/parameters/에서 캐시 관련 파라미터를 확인하고 조정할 수 있습니다. max_cache_size_bytes로 최대 캐시 크기를 설정하고, current_allocated_bytes로 현재 사용량을 모니터링합니다. dm-verity 워크로드에서 캐시 크기를 충분히 확보하면 해시 블록의 디스크 I/O를 대폭 줄일 수 있습니다.
dm-bufio 파라미터설명기본값
max_cache_size_bytes최대 캐시 크기가용 메모리의 25%
max_age_secondsLRU 최대 보유 시간300초
retain_bytesshrinker가 유지할 최소 크기262144 (256KB)
peak_allocated_bytes최대 할당 기록 (읽기 전용)-

DM Target 개발 완전 예제

Device Mapper target을 개발하려면 struct target_type의 콜백 함수를 구현하고 커널 모듈로 등록합니다. 이 섹션에서는 dm-linear를 클론한 완전한 모듈 예제를 통해 ctr(생성자), dtr(소멸자), map(I/O 매핑), status(상태 보고), message(런타임 명령) 콜백의 실제 구현 패턴을 상세히 살펴봅니다.

콜백 함수호출 시점역할
ctrdmsetup create / DM_TABLE_LOAD인자 파싱, 하위 디바이스 획득, private 데이터 초기화
dtr테이블 교체 / dmsetup remove하위 디바이스 해제, 메모리 정리
mapbio 도착 시 (I/O 경로)bio를 하위 디바이스로 remapping (bi_bdev, bi_sector 재설정)
statusdmsetup status / dmsetup table런타임 정보 또는 테이블 매개변수 출력
messagedmsetup message런타임 파라미터 변경 (통계 리셋 등)
end_io하위 디바이스 I/O 완료 시완료 후처리, 오류 핸들링, 통계 갱신
iterate_devicesDM 코어 내부하위 디바이스 순회 (디스카드, 토폴로지(Topology) 전파)
VFS / 파일시스템 bio 생성 DM Core dm_submit_bio() target lookup target.map(ti, bio) bio_set_dev() + bi_sector 재계산 DM_MAPIO_REMAPPED submit_bio_noacct(bio) 하위 블록 디바이스 (/dev/sda, /dev/nvme0n1) DM_MAPIO_SUBMITTED (target이 직접 제출) DM_MAPIO_KILL → bio_io_error()
map() 반환값: DM_MAPIO_REMAPPED는 DM 코어가 remapped bio를 제출하도록 지시합니다. DM_MAPIO_SUBMITTED는 target이 직접 bio를 제출했음을 의미하고, DM_MAPIO_KILL은 I/O 오류로 bio를 종료합니다. DM_MAPIO_REQUEUE는 나중에 재시도하도록 bio를 큐에 되돌립니다.

아래는 dm-linear를 클론한 완전한 커널 모듈 예제입니다. I/O 카운터와 message 콜백을 추가하여 런타임 통계 리셋 기능도 포함합니다.

/* dm-mylinear.c — dm-linear 클론 + I/O 카운터 + message 콜백 */
#include <linux/module.h>
#include <linux/device-mapper.h>
#include <linux/bio.h>

#define DM_MSG_PREFIX "mylinear"

struct mylinear_c {
    struct dm_dev *dev;
    sector_t start;
    atomic64_t read_ios;
    atomic64_t write_ios;
};

/* 생성자: dmsetup create 시 호출 */
static int mylinear_ctr(struct dm_target *ti,
                        unsigned int argc, char **argv)
{
    struct mylinear_c *lc;
    unsigned long long tmp;
    int r;

    if (argc != 2) {
        ti->error = "requires exactly 2 arguments: <dev_path> <offset>";
        return -EINVAL;
    }

    lc = kzalloc(sizeof(*lc), GFP_KERNEL);
    if (!lc) {
        ti->error = "cannot allocate private data";
        return -ENOMEM;
    }

    /* 하위 디바이스 획득 */
    r = dm_get_device(ti, argv[0],
                      dm_table_get_mode(ti->table), &lc->dev);
    if (r) {
        ti->error = "device lookup failed";
        kfree(lc);
        return r;
    }

    /* 오프셋 파싱 */
    if (sscanf(argv[1], "%llu", &tmp) != 1 ||
        tmp != (sector_t)tmp) {
        ti->error = "invalid offset";
        dm_put_device(ti, lc->dev);
        kfree(lc);
        return -EINVAL;
    }
    lc->start = tmp;

    atomic64_set(&lc->read_ios, 0);
    atomic64_set(&lc->write_ios, 0);
    ti->private = lc;
    ti->num_flush_bios = 1;
    ti->num_discard_bios = 1;
    ti->num_secure_erase_bios = 1;
    ti->num_write_zeroes_bios = 1;

    return 0;
}

/* 소멸자 */
static void mylinear_dtr(struct dm_target *ti)
{
    struct mylinear_c *lc = ti->private;
    dm_put_device(ti, lc->dev);
    kfree(lc);
}

/* I/O 매핑: 모든 bio에 대해 호출 */
static int mylinear_map(struct dm_target *ti,
                        struct bio *bio)
{
    struct mylinear_c *lc = ti->private;

    /* I/O 통계 수집 */
    if (bio_data_dir(bio) == READ)
        atomic64_inc(&lc->read_ios);
    else
        atomic64_inc(&lc->write_ios);

    /* bio를 하위 디바이스로 remapping */
    bio_set_dev(bio, lc->dev->bdev);
    if (bio_sectors(bio) || op_is_zone_mgmt(bio->bi_opf))
        bio->bi_iter.bi_sector = lc->start +
            dm_target_offset(ti, bio->bi_iter.bi_sector);

    return DM_MAPIO_REMAPPED;
}

/* 상태 보고 */
static void mylinear_status(struct dm_target *ti,
    status_type_t type, unsigned int status_flags,
    char *result, unsigned int maxlen)
{
    struct mylinear_c *lc = ti->private;

    switch (type) {
    case STATUSTYPE_INFO:
        snprintf(result, maxlen,
                 "reads=%lld writes=%lld",
                 atomic64_read(&lc->read_ios),
                 atomic64_read(&lc->write_ios));
        break;
    case STATUSTYPE_TABLE:
        snprintf(result, maxlen, "%s %llu",
                 lc->dev->name, (unsigned long long)lc->start);
        break;
    case STATUSTYPE_IMA:
        *result = '\0';
        break;
    }
}

/* message 콜백: 런타임 명령 */
static int mylinear_message(struct dm_target *ti,
    unsigned int argc, char **argv,
    char *result, unsigned int maxlen)
{
    struct mylinear_c *lc = ti->private;

    if (argc == 1 && !strcasecmp(argv[0], "reset_stats")) {
        atomic64_set(&lc->read_ios, 0);
        atomic64_set(&lc->write_ios, 0);
        DMINFO("statistics reset");
        return 0;
    }
    DMERR("unrecognised message: %s", argv[0]);
    return -EINVAL;
}

/* 하위 디바이스 순회 */
static int mylinear_iterate_devices(struct dm_target *ti,
    iterate_devices_callout_fn fn, void *data)
{
    struct mylinear_c *lc = ti->private;
    return fn(ti, lc->dev, lc->start, ti->len, data);
}

static struct target_type mylinear_target = {
    .name            = "mylinear",
    .version         = {1, 0, 0},
    .features        = DM_TARGET_PASSES_INTEGRITY,
    .module          = THIS_MODULE,
    .ctr             = mylinear_ctr,
    .dtr             = mylinear_dtr,
    .map             = mylinear_map,
    .status          = mylinear_status,
    .message         = mylinear_message,
    .iterate_devices = mylinear_iterate_devices,
};

static int __init dm_mylinear_init(void)
{
    return dm_register_target(&mylinear_target);
}

static void __exit dm_mylinear_exit(void)
{
    dm_unregister_target(&mylinear_target);
}

module_init(dm_mylinear_init);
module_exit(dm_mylinear_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example");
MODULE_DESCRIPTION("DM mylinear — linear clone with I/O stats");
# Makefile — out-of-tree DM target 빌드
obj-m += dm-mylinear.o

KDIR ?= /lib/modules/$(shell uname -r)/build

all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean
빌드 팁: 커널 헤더(linux-headers-$(uname -r))가 설치되어 있어야 합니다. make로 빌드한 뒤 insmod dm-mylinear.ko로 로드하면 dmsetup targetsmylinear가 나타납니다.
# 모듈 로드 및 DM 디바이스 생성
sudo insmod dm-mylinear.ko
dmsetup targets | grep mylinear
# mylinear        v1.0.0

# 루프 디바이스 생성 (테스트용 1GB)
dd if=/dev/zero of=/tmp/test.img bs=1M count=1024
sudo losetup /dev/loop0 /tmp/test.img

# DM 디바이스 생성: 0 <sectors> mylinear <dev> <offset>
# 1GB = 2097152 sectors (512B 단위)
echo "0 2097152 mylinear /dev/loop0 0" | sudo dmsetup create mytest

# 동작 확인
ls -la /dev/mapper/mytest
sudo mkfs.ext4 /dev/mapper/mytest
sudo mount /dev/mapper/mytest /mnt/test
echo "hello dm" | sudo tee /mnt/test/hello.txt

# I/O 통계 확인
sudo dmsetup status mytest
# 0 2097152 mylinear reads=42 writes=15

# 통계 리셋
sudo dmsetup message mytest 0 reset_stats
sudo dmsetup status mytest
# 0 2097152 mylinear reads=0 writes=0

# 정리
sudo umount /mnt/test
sudo dmsetup remove mytest
sudo losetup -d /dev/loop0
sudo rmmod dm-mylinear
# 검증: dmesg에서 모듈 로그 확인
dmesg | grep mylinear
# [  123.456] device-mapper: mylinear: statistics reset

# /proc 확인
cat /proc/devices | grep device-mapper
# 253 device-mapper

# sysfs에서 DM 디바이스 확인
ls /sys/block/dm-0/dm/
# name  suspended  uuid
cat /sys/block/dm-0/dm/name
# mytest
주의: out-of-tree DM target은 커널 버전 업그레이드 시 API 변경으로 빌드가 깨질 수 있습니다. 프로덕션에서는 in-tree 통합을 권장하며, drivers/md/KconfigMakefile에 항목을 추가하여 커널 빌드 시스템(Build System)에 포함시키는 것이 바람직합니다.

blktrace/btrace 분석

blktrace는 블록 레이어의 I/O 이벤트를 실시간으로 추적하는 도구로, DM 디바이스의 I/O 패턴을 분석하는 데 필수적입니다. dm-verity 환경에서는 해시 트리 읽기와 데이터 블록 읽기가 인터리빙되는 패턴을 관찰할 수 있습니다.

Action 코드의미설명
QQueuedbio가 request queue에 진입
GGet requestrequest 구조체(Struct) 할당
IInsertedI/O 스케줄러에 삽입
DIssued드라이버에 디스패치(Dispatch)
CCompletedI/O 완료
MMerged기존 request에 병합
ARemapDM/MD에 의한 remap (DM 분석의 핵심)
XSplitbio가 분할됨
# blktrace 기본 사용법: dm-verity 디바이스 추적
# -d: 대상 디바이스, -o -: stdout 출력
sudo blktrace -d /dev/dm-0 -o - | blkparse -i - -o trace.out

# btrace: blktrace + blkparse 간편 래퍼
sudo btrace /dev/dm-0

# 특정 시간(10초) 동안 추적
sudo blktrace -d /dev/dm-0 -w 10 -o verity_trace
blkparse -i verity_trace -d verity_trace.bin

# DM 하위 디바이스도 동시 추적 (remap 관계 확인)
sudo blktrace -d /dev/dm-0 -d /dev/sda -w 5 -o multi_trace
blkparse -i multi_trace -M -o multi_out.txt
# dm-verity I/O 패턴 분석 예제
# A(remap) 이벤트만 필터링: DM 리매핑 추적
blkparse -i verity_trace -a remap -o remap_only.txt

# 출력 예시 해석:
# 253,0  0  1  0.000000000  1234 A  R  2048 + 8  <- (8,0) 4096
#   │    │  │       │        │   │  │   │     │       │      └ 하위 디바이스 섹터
#   │    │  │       │        │   │  │   │     │       └ 하위 디바이스 (major,minor)
#   │    │  │       │        │   │  │   │     └ 길이 (섹터)
#   │    │  │       │        │   │  │   └ DM 디바이스 섹터
#   │    │  │       │        │   │  └ R=Read, W=Write
#   │    │  │       │        │   └ A=remap
#   │    │  │       │        └ PID
#   │    │  │       └ 타임스탬프
#   └────┘──┘ 디바이스 (major,minor) + CPU

# dm-verity 패턴: 해시 블록 읽기 + 데이터 블록 읽기 반복
# hash_dev 읽기 (상위 Merkle 노드) → data_dev 읽기 (실제 데이터)

# btt: blktrace timing 통계 분석
btt -i verity_trace.bin -l verity_latency.dat
# Q2C: 전체 레이턴시 (요청~완료)
# D2C: 디바이스 레이턴시 (디스패치~완료)
# Q2G: 큐잉 레이턴시

# iowatcher: blktrace 결과를 시각화 SVG로 변환
iowatcher -t verity_trace -o verity_io.svg
DM remap 추적 팁: dm-verity 디바이스에서 A(remap) 이벤트를 분석하면 데이터 블록 하나를 읽을 때 발생하는 해시 블록 I/O 횟수를 파악할 수 있습니다. Merkle Tree의 깊이가 d이면, 캐시 미스 시 최대 d개의 해시 블록 읽기가 선행됩니다. btt의 Q2C 통계로 dm-verity 오버헤드를 정량적으로 측정하세요.

FIPS 140-2/3 고려사항

FIPS(Federal Information Processing Standards) 140-2/3은 미국 정부가 요구하는 암호화 모듈 인증 표준입니다. 리눅스 커널이 FIPS 모드로 동작할 때 dm-crypt, dm-integrity, dm-verity가 받는 제약과 설정 방법을 정리합니다.

구분FIPS 승인 알고리즘FIPS 비승인 (사용 불가)
블록 암호AES-128/192/256 (CBC, XTS, GCM, CCM)Blowfish, Twofish, Serpent, Camellia
해시SHA-1, SHA-224/256/384/512, SHA-3MD5, RIPEMD, Whirlpool
MACHMAC-SHA-256, HMAC-SHA-512, CMAC-AESHMAC-MD5
KDFPBKDF2-SHA-256, Argon2id (FIPS 140-3 SP 800-132)scrypt (일부 모듈에서 비승인)
난수DRBG (SP 800-90A: CTR_DRBG, HMAC_DRBG)커널 기본 /dev/urandom (비인증)
# 커널 FIPS 모드 확인
cat /proc/sys/crypto/fips_enabled
# 1 = FIPS 모드 활성화

# FIPS 모드 부팅 활성화 (GRUB)
# /etc/default/grub에 추가:
# GRUB_CMDLINE_LINUX="fips=1"
sudo update-grub && sudo reboot

# FIPS self-test 결과 확인
dmesg | grep -i fips
# [    0.123] fips: FIPS 140-3 self-tests passed
# [    0.456] alg: self-tests for aes passed
# [    0.789] alg: self-tests for sha256 passed
# FIPS 모드에서 dm-crypt 설정 (승인 알고리즘만 사용)
# AES-XTS-256: FIPS 승인 ✓
sudo cryptsetup luksFormat --type luks2 \
    --cipher aes-xts-plain64 --key-size 512 \
    --hash sha256 --pbkdf argon2id \
    /dev/sda2

# FIPS 비승인 알고리즘 시도 → 실패
sudo cryptsetup luksFormat --cipher serpent-xts-plain64 /dev/sda2
# Error: cipher serpent-xts-plain64 not available in FIPS mode

# dm-verity: SHA-256 사용 (FIPS 승인)
sudo veritysetup format --hash sha256 /dev/sda1 /dev/sda2
# ✓ FIPS 모드에서 정상 동작

# dm-integrity + HMAC: FIPS 승인 해시 필수
sudo integritysetup format --integrity hmac-sha256 \
    --integrity-key-size 32 --integrity-key-file /path/to/key \
    /dev/sda3
FIPS 부팅 시간 영향: FIPS 모드 활성화 시 커널은 부팅 과정(Boot Process)에서 모든 암호 알고리즘의 self-test를 수행합니다. 이로 인해 부팅 시간이 2-5초 증가할 수 있으며, 임베디드 시스템에서는 이 오버헤드가 중요할 수 있습니다. self-test 실패 시 해당 알고리즘은 사용 불가능해지며, dm-crypt/dm-verity 활성화가 실패합니다.

LUKS1 vs LUKS2 비교

LUKS(Linux Unified Key Setup)는 dm-crypt 위에서 키 관리, 헤더 포맷, 다중 패스프레이즈를 표준화한 디스크 암호화 포맷입니다. LUKS2는 JSON 메타데이터, Argon2id KDF, 토큰/세그먼트 추상화, dm-integrity 연동 등 대폭 개선된 2세대 포맷입니다.

LUKS1 (고정 바이너리 구조) LUKS2 (JSON 메타데이터) LUKS1 phdr (592 bytes) magic | version | cipher | hash | mk-digest | salt Key Slots (최대 8개, 고정 크기) KS0 KS1 KS2 ... KS7 Key Material Area (AF split) 암호화된 데이터 영역 payload_offset 이후 Binary Header (4KB) + Primary JSON Area Secondary JSON Area (이중화 복구) Keyslots (가변 크기, 무제한) Argon2id | PBKDF2 | 하드웨어 토큰 Segments: 데이터 영역 매핑 정의 Tokens (FIDO2/TPM2) dm-integrity 연동 암호화된 데이터 영역 Segments로 유연한 매핑 PBKDF2 전용 온라인 리키잉 불가 Argon2id 기본 (GPU 저항) 온라인 리키잉 + AEAD 지원
기능LUKS1LUKS2
헤더 포맷고정 바이너리 (592 bytes phdr)JSON 메타데이터 (이중화)
최대 키 슬롯8개 (고정 크기)사실상 무제한 (가변 크기)
KDFPBKDF2 전용Argon2id (기본) / PBKDF2
무결성 연동미지원dm-integrity (AEAD: AES-GCM/CHACHA20-POLY1305)
온라인 리키잉불가cryptsetup reencrypt (온라인)
토큰미지원FIDO2, TPM2, systemd-cryptenroll
세그먼트단일 (전체 디바이스)다중 세그먼트 (부분 암호화 가능)
헤더 복구단일 헤더 (손상 시 복구 어려움)Primary + Secondary (자동 복구)
호환성cryptsetup 1.0+cryptsetup 2.0+
LUKS2 JSON 객체설명
keyslots키 슬롯 정의 (KDF 파라미터, AF 스트라이프, 암호화된 키 위치)
segments데이터 영역 매핑 (오프셋, 크기, 암호, IV 모드)
tokens외부 잠금해제 메커니즘 (TPM2 sealed key, FIDO2, Clevis)
digests키 검증용 다이제스트 (keyslot → volume key 매핑)
configJSON 영역 크기, 키슬롯 영역 크기 설정
# LUKS1 → LUKS2 마이그레이션
# 주의: 반드시 백업 후 진행!
sudo cryptsetup luksDump /dev/sda2 | head -5
# LUKS header information
# Version:        1

# LUKS1 → LUKS2 변환
sudo cryptsetup convert --type luks2 /dev/sda2
sudo cryptsetup luksDump /dev/sda2 | head -5
# LUKS header information
# Version:        2

# LUKS2에서 Argon2id로 KDF 업그레이드
sudo cryptsetup luksConvertKey --pbkdf argon2id \
    --pbkdf-memory 1048576 --pbkdf-parallel 4 /dev/sda2

# LUKS2 JSON 메타데이터 확인
sudo cryptsetup luksDump --dump-json-metadata /dev/sda2 | python3 -m json.tool
# LUKS2 + dm-integrity: 인증 암호화 (AEAD)
# AES-256-GCM: 암호화 + 무결성 동시 보장
sudo cryptsetup luksFormat --type luks2 \
    --cipher aes-gcm-random --key-size 256 \
    --integrity aead \
    --sector-size 4096 \
    /dev/sda2

# 내부적으로 dm-crypt + dm-integrity 스택 생성
sudo cryptsetup open /dev/sda2 secure_vol
dmsetup table secure_vol
# 0 <size> crypt aes-gcm-random ... integrity:28:aead

# 온라인 리키잉 (LUKS2 전용)
# 사용 중인 볼륨의 마스터 키를 교체
sudo cryptsetup reencrypt --cipher aes-xts-plain64 \
    --key-size 512 /dev/sda2
LUKS2 권장: 신규 설치에서는 항상 LUKS2를 사용하세요. Argon2id KDF는 GPU/ASIC 브루트포스 공격에 대한 저항성이 PBKDF2보다 훨씬 우수하며, dm-integrity 연동으로 암호화된 데이터의 무결성까지 보장할 수 있습니다.
LUKS1 → LUKS2 변환 주의: 변환 전 반드시 cryptsetup luksHeaderBackup으로 헤더를 백업하세요. GRUB2의 LUKS2 지원은 제한적이므로 (Argon2id 미지원 등) 부트 파티션 암호화에는 LUKS1 또는 PBKDF2 기반 LUKS2를 사용해야 할 수 있습니다.

하드웨어 가속

최신 스토리지 컨트롤러와 프로세서는 인라인 암호화 엔진을 내장하여 dm-crypt의 소프트웨어 암호화 오버헤드를 제거합니다. 커널 5.9+의 blk-crypto 프레임워크는 하드웨어/소프트웨어 폴백을 투명하게 통합합니다.

파일시스템 (ext4/f2fs) fscrypt (파일 단위 암호화) per-file key → crypto_context bio + bio_crypt_ctx key, DUN (Data Unit Number), algorithm blk-crypto 프레임워크 blk_crypto_profile → HW 지원 여부 판단 HW 지원 HW 미지원 HW Inline Crypto Engine Qualcomm ICE / Samsung FBE / UFS SW Fallback (blk-crypto-fallback) 커널 crypto API (AES-NI 가속)
하드웨어 가속벤더방식성능 특성
Inline Crypto Engine (ICE)QualcommUFS/eMMC 컨트롤러 내장 AES 엔진CPU 사용률 0%, 라인 속도 암호화
FBE HWSamsungeMMC/UFS 인라인 암호화CPU 사용률 0%, Knox 보안 연동
AES-NIIntel/AMDCPU 명령어 셋 (소프트웨어 가속)CPU 사용, 3-5 GB/s (코어당)
ARMv8 CEARMCrypto Extensions 명령어CPU 사용, 1-3 GB/s (코어당)
CCPAMDCryptographic Co-Processor오프로드, DMA 기반
QATIntelQuickAssist Technology가속 카드, 대량 암호화/압축
# blk-crypto 하드웨어 지원 확인
# UFS 컨트롤러의 crypto capabilities
cat /sys/block/sda/queue/crypto/modes/AES-256-XTS
# 512  (지원하는 데이터 유닛 크기)

# blk-crypto 프로파일 확인
ls /sys/block/sda/queue/crypto/
# max_dun_bits  modes/  num_keyslots

cat /sys/block/sda/queue/crypto/num_keyslots
# 32  (하드웨어 키 슬롯 수)

# Intel AES-NI 지원 확인
grep -o aes /proc/cpuinfo | head -1
# aes
lsmod | grep aesni
# aesni_intel  ...
/* blk-crypto 키 설정 예시 (커널 내부) */
struct blk_crypto_key key;

/* 키 초기화 */
blk_crypto_init_key(&key, raw_key, 64,
    BLK_ENCRYPTION_MODE_AES_256_XTS, 0,
    512);  /* data_unit_size */

/* bio에 crypto context 설정 */
bio_crypt_set_ctx(bio, &key, dun, GFP_NOIO);

/* blk-crypto가 자동으로 판단:
 * - HW 지원 → 인라인 엔진에 키 프로그래밍
 * - HW 미지원 → blk-crypto-fallback (SW 암호화) */
blk-crypto 아키텍처: blk-crypto는 파일시스템(fscrypt)과 블록 디바이스 사이에서 투명하게 동작합니다. 하드웨어 인라인 엔진이 있으면 bio의 crypto context를 하드웨어에 전달하고, 없으면 자동으로 소프트웨어 폴백(blk-crypto-fallback.c)으로 전환합니다. 이 덕분에 fscrypt 코드는 하드웨어 존재 여부를 알 필요가 없습니다.

배치 검증 최적화

dm-verity의 읽기 성능은 Merkle Tree 해시 블록의 I/O 오버헤드에 크게 좌우됩니다. 커널은 해시 블록 프리페치(prefetch), dm-bufio 캐싱, readahead 조합을 통해 대용량 순차 읽기 시나리오에서 검증 오버헤드를 최소화합니다.

최적화 기법구현 위치효과
해시 블록 프리페치verity_prefetch_io()데이터 읽기 전 해시 블록을 미리 로드
dm-bufio 페이지 캐시(Page Cache)dm_bufio_read()해시 블록을 메모리에 캐싱하여 디스크 I/O 절약
프리페치 클러스터DM_VERITY_DEFAULT_PREFETCH_SIZE한 번에 여러 해시 블록을 묶어 읽기
readahead 연동블록 레이어 readahead순차 읽기 시 데이터+해시 블록 사전 로드
FEC 인터리빙 최적화verity_fec_decode()FEC 블록도 클러스터 프리페치
/* dm-verity-target.c: 프리페치 워커 */
static void verity_prefetch_io(struct work_struct *work)
{
    struct dm_verity_prefetch_work *pw =
        container_of(work, struct dm_verity_prefetch_work, work);
    struct dm_verity *v = pw->v;
    int i;

    /* Merkle Tree 각 레벨의 해시 블록을 프리페치 */
    for (i = v->levels - 1; i >= 0; i--) {
        sector_t hash_block_start, hash_block_end;

        /* 데이터 블록 범위 → 해시 블록 범위 변환 */
        verity_hash_at_level(v, pw->block, i,
                            &hash_block_start, NULL);
        verity_hash_at_level(v, pw->block + pw->n_blocks - 1,
                            i, &hash_block_end, NULL);

        /* dm-bufio prefetch: 비동기 읽기 요청 */
        dm_bufio_prefetch(v->bufio, hash_block_start,
                         hash_block_end - hash_block_start + 1);
    }

    kfree(pw);
}

/* dm-verity map 경로에서 프리페치 스케줄링 */
static void verity_submit_prefetch(struct dm_verity *v,
    struct dm_verity_io *io)
{
    struct dm_verity_prefetch_work *pw;

    pw = kmalloc(sizeof(*pw), GFP_NOIO);
    if (!pw)
        return;

    INIT_WORK(&pw->work, verity_prefetch_io);
    pw->v = v;
    pw->block = io->block;
    pw->n_blocks = io->n_blocks;
    queue_work(v->verify_wq, &pw->work);
}
# dm-bufio 캐시 상태 확인
cat /sys/module/dm_bufio/parameters/current_allocated_bytes
# 67108864  (64MB)

# 캐시 크기 조정 (해시 트리 전체를 캐싱할 만큼 확보)
# 1GB 디바이스 + SHA-256 + 4KB 블록 = 약 8MB 해시 트리
echo 134217728 | sudo tee /sys/module/dm_bufio/parameters/max_cache_size_bytes
# 128MB로 확대

# readahead 크기 조정 (DM 디바이스)
blockdev --getra /dev/dm-0
# 256  (128KB, 기본값)
sudo blockdev --setra 2048 /dev/dm-0
# 1MB로 확대 → 순차 읽기 성능 향상

# 프리페치 효과 측정: fio 순차 읽기
fio --name=seqread --filename=/dev/dm-0 --rw=read \
    --bs=128k --numjobs=4 --iodepth=32 --size=1G \
    --direct=1 --group_reporting

# dm-bufio 캐시 히트율 확인
cat /sys/module/dm_bufio/parameters/current_allocated_bytes
# peak vs current로 캐시 워킹셋 파악
cat /sys/module/dm_bufio/parameters/peak_allocated_bytes
성능 팁: dm-verity의 해시 트리 크기는 데이터 크기에 비해 매우 작습니다 (예: 4GB 데이터 → ~32MB 해시 트리). max_cache_size_bytes를 해시 트리 전체 크기 이상으로 설정하면 steady-state에서 해시 블록의 디스크 I/O가 0에 수렴합니다. Android에서는 부팅 초기에 전체 해시 트리를 프리로드하는 최적화를 적용하여 verity_prefetch_io 워커가 부팅 직후 해시 트리를 워밍업합니다.
내부 문서:
외부 참고 자료: