dm-verity — Merkle Tree 기반 블록 검증

dm-verity는 Linux Device Mapper 프레임워크 위에서 Merkle Tree 기반의 읽기 전용 블록 무결성 검증을 제공합니다. Android Verified Boot(AVB)의 핵심 메커니즘으로, 빌드 시 생성된 Merkle Tree의 루트 해시를 런타임에 블록 단위로 검증하여 시스템 파티션의 변조를 탐지합니다. Forward Error Correction(FEC)을 통한 자동 복구, dm-verity-loadpin을 통한 모듈 로드 제한, 그리고 배치 검증 최적화까지 커널 내부 구현(drivers/md/dm-verity-target.c)을 심층 분석합니다.

관련 Device Mapper 문서:
전제 조건: 블록 I/O 문서와 Device Mapper / LVM 문서를 먼저 읽으세요. 블록 디바이스의 기본 I/O 경로와 Device Mapper의 target 개념을 이해해야 합니다.
일상 비유: dm-verity는 택배 상자의 봉인 스티커와 비슷합니다. 상자(디스크 블록)를 열기 전에 봉인(해시(Hash))이 손상되지 않았는지 확인합니다. 봉인이 뜯어졌다면 내용물이 변조되었을 가능성이 있으므로 상자를 거부합니다.

핵심 요약

  • Device Mapper — 블록 디바이스 위에 가상 매핑(Mapping) 레이어를 제공하는 커널 프레임워크
  • dm-verity — Merkle Tree 기반 읽기 전용(Read-Only) 블록 무결성 검증 (Android Verified Boot 핵심)
  • FEC (Forward Error Correction) — Reed-Solomon 부호를 사용한 손상 블록 자동 복구
  • dm-verity-loadpin — dm-verity로 검증된 파티션에서만 커널 모듈/펌웨어 로드 허용

단계별 이해

  1. Device Mapper 레이어 이해
    커널의 블록 I/O 스택에서 DM은 bio를 가로채 target 드라이버에 전달합니다.
  2. dm-verity로 읽기 검증
    빌드 시 Merkle Tree를 생성하고, 런타임에 블록 단위로 해시를 검증합니다.
  3. Android Verified Boot 체인
    부트로더 → vbmeta 서명 검증 → 커널 cmdline에 루트 해시 전달 → dm-verity 활성화 순서로 무결성을 보장합니다.
  4. FEC로 자동 복구
    Reed-Solomon 부호를 추가하여 비트 에러로 인한 검증 실패 시 자동 복구를 시도합니다.
  5. LoadPin으로 모듈 로드 제한
    dm-verity로 검증된 파티션에서만 커널 모듈과 펌웨어를 로드하도록 강제합니다.

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-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)을 쌓아 업데이트 중에도 시스템 무결성을 유지합니다.

배치 검증 최적화

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 워커가 부팅 직후 해시 트리를 워밍업합니다.

블록 레벨 해시 체인 검증

dm-verity의 블록 레벨 검증은 데이터 블록 읽기 요청이 들어올 때마다 Merkle Tree를 bottom-up으로 순회하며 해시를 검증합니다. 이 과정에서 각 레벨의 해시 블록을 dm_bufio 캐시에서 읽어와 계산된 해시와 저장된 해시를 비교합니다. 한 번 검증된 해시 블록은 캐시에 "verified" 상태로 표시되어 재검증 비용을 절약합니다.

블록 N 읽기 요청 시 해시 체인 검증 과정 1. 데이터 블록 N 읽기 submit_bio_noacct(bio) 2. SHA-256(Block N) 계산 verity_hash_for_block() 3. Level 0 해시 비교 hash_block[N/128]에서 조회 dm_bufio 캐시 조회 캐시 히트 시 디스크 I/O 건너뜀 4. Level 1 검증: SHA-256(L0_block) L0 블록 전체를 해싱하여 L1 해시와 비교 5. Level 2 검증: SHA-256(L1_block) 반복하여 루트 해시까지 검증 6. Root Hash 최종 비교 v->root_digest와 일치 확인 검증 성공: bio_endio() 실패: FEC 시도 / -EIO 해시 체인 검증 단계 요약 1. 데이터 블록을 디스크에서 읽음 2. SHA-256(salt || data_block) 해시 계산 3. Level 0 해시 블록에서 기대 해시 조회 및 비교 4. Level 0 블록 자체를 해싱하여 Level 1과 비교 5. 루트 해시까지 반복 (보통 3-4 레벨)
/* drivers/md/dm-verity-target.c - 레벨별 해시 검증 */
static int verity_verify_level(struct dm_verity *v,
                               struct dm_verity_io *io,
                               sector_t block, int level,
                               bool skip_unverified,
                               u8 *want_digest)
{
    struct dm_buffer *buf;
    struct buffer_aux *aux;
    u8 *data;
    sector_t hash_block;
    unsigned int offset;

    /* 해당 블록의 해시 블록 위치와 오프셋 계산 */
    verity_hash_at_level(v, block, level, &hash_block, &offset);

    /* dm_bufio로 해시 블록 읽기 (캐시 우선) */
    data = dm_bufio_read(v->bufio, hash_block, &buf);
    if (IS_ERR(data))
        return PTR_ERR(data);

    aux = dm_bufio_get_aux_data(buf);

    /* 이미 검증된 해시 블록인지 확인 */
    if (!aux->hash_verified) {
        if (skip_unverified) {
            dm_bufio_release(buf);
            return 1;  /* 미검증 블록 건너뜀 */
        }

        /* 상위 레벨에서 이 해시 블록의 해시를 검증 */
        r = verity_verify_level(v, io, hash_block,
                               level + 1, 0, NULL);
        if (r)
            goto release;

        aux->hash_verified = 1;  /* 검증 완료 표시 */
    }

    /* 기대 해시 값 추출 (offset 위치) */
    memcpy(want_digest, data + offset, v->digest_size);

release:
    dm_bufio_release(buf);
    return r;
}

/* 해시 블록 위치 계산 유틸리티 */
static void verity_hash_at_level(struct dm_verity *v,
                                  sector_t block, int level,
                                  sector_t *hash_block,
                                  unsigned int *offset)
{
    sector_t position;

    /* 블록 번호를 해당 레벨의 팬아웃으로 나누어 위치 계산
     * SHA-256, 4KB 블록: hash_per_block_bits = 7 (128개) */
    position = block >> (v->hash_per_block_bits * (level + 1));
    *hash_block = v->hash_level_block[level] + position;

    if (offset) {
        position = (block >> (v->hash_per_block_bits * level))
                   & (((sector_t)1 << v->hash_per_block_bits) - 1);
        *offset = position * v->digest_size;
    }
}
해시 체인 검증의 재귀적 특성: verity_verify_level()은 재귀적으로 상위 레벨을 검증합니다. Level 0에서 시작하면 Level 1, Level 2, ... Root까지 올라갑니다. aux->hash_verified 플래그 덕분에 이미 검증된 해시 블록은 즉시 반환되어 중복 검증을 피합니다. 순차 읽기에서 인접 블록들은 동일한 해시 블록을 공유하므로 캐시 히트율이 매우 높습니다.

FEC 내부 복구 메커니즘

Forward Error Correction(FEC)의 내부 동작은 Reed-Solomon 부호의 인터리빙(Interleaving)에 기반합니다. dm-verity FEC는 데이터와 해시 영역 전체를 RS 블록 단위로 인터리빙하여 인코딩합니다. 검증 실패 시 verity_fec_decode()가 호출되어 손상된 바이트의 위치를 특정하고 RS 디코딩으로 복구를 시도합니다.

FEC Reed-Solomon 인터리빙 구조 데이터+해시 인터리빙 행렬 B[0] B[R] B[2R] ... Parity[0] B[1] B[R+1] B[2R+1] ... Parity[1] RS 코드워드 0 RS 코드워드 1 (손상됨) 인터리빙 원리 R = rounds = ceil(total_blocks / rsn), rsn = 253 (RS(255,253)) 각 RS 코드워드는 R 간격의 바이트로 구성 (버스트 에러 분산) FEC 복구 흐름 verity_verify 실패 verity_fec_decode() 호출 손상 바이트의 RS 코드워드 식별 fec_decode_rsb() decode_rs8() RS 디코딩 lib/reed_solomon/decode_rs.c 정정된 데이터로 교체 해시 재검증 (정정 확인) 디코딩 실패: -EIO FEC 통계 (dmsetup status 출력) V - 검증 성공 블록 수 C - 검증 실패 블록 수 (corruption 감지) F - FEC 복구 성공 블록 수 U - FEC 복구 실패 블록 수 (uncorrectable)
/* drivers/md/dm-verity-fec.c - FEC 디코딩 핵심 */
static int fec_decode_rsb(struct dm_verity *v,
                          struct dm_verity_io *io,
                          struct dm_verity_fec_io *fio,
                          u64 rsb, unsigned int offset,
                          bool use_erasures)
{
    int r, neras = 0;
    unsigned int i, pos;
    u8 *block = fio->rsb;

    /* RS 코드워드 구성: 인터리빙된 바이트 수집 */
    for (i = 0; i < v->fec->rsn; i++) {
        u64 byte_pos = rsb + i * v->fec->rounds;
        sector_t block_num = byte_pos / (1 << v->data_dev_block_bits);

        /* 데이터/해시 영역에서 바이트 읽기 */
        r = fec_read_bufs(v, io, rsb, byte_pos, block + i);
        if (r)
            return r;
    }

    /* FEC 패리티 바이트 읽기 */
    for (i = 0; i < v->fec->roots; i++) {
        r = fec_read_parity(v, rsb, i, &block[v->fec->rsn + i]);
        if (r)
            return r;
    }

    /* erasure 위치 힌트 (verity 실패 블록의 바이트) */
    if (use_erasures) {
        pos = offset / v->fec->rounds;
        fio->erasures[neras++] = pos;
    }

    /* Reed-Solomon 디코딩 수행 */
    r = decode_rs8(fio->rs, block, block + v->fec->rsn,
                   v->fec->rsn, NULL, neras,
                   fio->erasures, 0, NULL);

    if (r < 0)
        return r;  /* 복구 불가 */

    /* 정정된 바이트를 원래 위치에 기록 */
    fec_write_corrected(v, fio, rsb, block);

    return 0;  /* r = 정정된 바이트 수 */
}

/* FEC 복구 진입점 */
int verity_fec_decode(struct dm_verity *v,
                      struct dm_verity_io *io,
                      enum verity_block_type type,
                      sector_t block, u8 *dest,
                      struct bvec_iter *iter)
{
    struct dm_verity_fec_io *fio = fec_io(io);
    int r;

    if (!v->fec)
        return -EOPNOTSUPP;

    /* 손상 블록의 모든 RS 코드워드를 디코딩 시도 */
    for (offset = 0; offset < 1 << v->data_dev_block_bits; offset++) {
        u64 rsb = fec_rsb(v, block, offset);
        r = fec_decode_rsb(v, io, fio, rsb, offset, 1);
        if (r < 0)
            return r;
    }

    /* 정정된 데이터로 해시 재검증 */
    r = verity_hash(v, verity_io_hash_req(v, io),
                    dest, 1 << v->data_dev_block_bits,
                    verity_io_real_digest(v, io));
    if (r)
        return r;

    return memcmp(verity_io_real_digest(v, io),
                   verity_io_want_digest(v, io),
                   v->digest_size) ? -EILSEQ : 0;
}
FEC 복구의 한계: RS(255,253)에서 코드워드당 최대 1바이트 에러만 정정 가능합니다. 인터리빙 덕분에 연속적인 비트 에러도 분산되지만, 대규모 블록 손상(예: bad sector 수십 개)은 복구가 불가능합니다. FEC는 플래시 메모리의 비트 에러와 같은 소규모 손상에 특화되어 있으며, 의도적 변조에는 설계 목적이 아닙니다.

dm-verity + dm-integrity 결합 구성

dm-verity와 dm-integrity는 서로 다른 사용 사례를 위해 설계되었지만, 특정 시나리오에서는 두 가지를 결합하여 사용할 수 있습니다. 읽기 전용 시스템 파티션에는 dm-verity를, 읽기/쓰기 데이터 파티션에는 dm-integrity를 적용하여 전체 스토리지 스택의 무결성을 보장하는 구성이 대표적입니다.

dm-verity + dm-integrity 결합 스택 구성 /system (ext4, 읽기 전용) /data (ext4, 읽기/쓰기) dm-verity Merkle Tree 기반 읽기 전용 검증 dm-crypt (AES-XTS) 블록 레벨 암호화 (LUKS2) dm-integrity (HMAC-SHA256) 저널링 무결성 태그, crash-safety /dev/sda1 (시스템 파티션) /dev/sda2 (데이터 파티션) 해시 디바이스 FEC 디바이스 결합 구성의 장점: 시스템 파티션은 변조 불가 (dm-verity), 데이터 파티션은 암호화+무결성 (dm-crypt+dm-integrity) dm-integrity의 저널링이 데이터 파티션의 crash-safety를 보장하며, dm-verity는 시스템 무결성을 보증합니다
# 결합 구성 예제: dm-verity (시스템) + dm-crypt+dm-integrity (데이터)

## 1단계: 시스템 파티션 - dm-verity 설정
# 빌드 시 해시 트리 생성
$ veritysetup format --hash sha256 --fec-device /dev/sda3 --fec-roots 2 \
    /dev/sda1 /dev/sda2
# Root hash: 4a5b6c7d...

# 부팅 시 활성화
$ veritysetup open /dev/sda1 system_verified /dev/sda2 \
    --fec-device=/dev/sda3 --fec-roots=2 \
    4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d...
$ mount -o ro /dev/mapper/system_verified /system

## 2단계: 데이터 파티션 - dm-crypt + dm-integrity (AEAD) 설정
$ cryptsetup luksFormat --type luks2 \
    --cipher aes-xts-plain64 \
    --integrity hmac-sha256 \
    --sector-size 4096 \
    --key-size 512 \
    /dev/sda4

# dm-integrity가 자동으로 하위 레이어에 생성됨
$ cryptsetup open /dev/sda4 data_encrypted
$ mount /dev/mapper/data_encrypted /data

## 스택 확인
$ dmsetup ls --tree
data_encrypted   (254:2)
 \`-data_encrypted_dif (254:1)  # dm-integrity 레이어
system_verified  (254:0)        # dm-verity 레이어

## 각 레이어 상태 확인
$ dmsetup status system_verified
# 0 4194304 verity V 0 C 0
$ dmsetup status data_encrypted_dif
# 0 2097152 integrity 0 0 recalculating
구성 요소보호 대상검증 방식읽기/쓰기
dm-verity시스템 파티션 (OS, 앱)Merkle Tree + FEC읽기 전용
dm-crypt데이터 파티션 (사용자 데이터)AES-XTS 암호화읽기/쓰기
dm-integrity데이터 파티션 무결성HMAC-SHA256 태그읽기/쓰기
dm-verity FEC시스템 파티션 복구Reed-Solomon읽기 전용

성능 벤치마크 및 튜닝

dm-verity의 성능 오버헤드는 해시 계산 비용과 해시 블록 I/O 비용으로 나뉩니다. 최신 CPU의 SHA-NI 확장(Extension)을 활용하면 해시 계산 비용은 크게 줄일 수 있으며, dm-bufio 캐시를 충분히 확보하면 해시 블록 I/O도 최소화할 수 있습니다.

# dm-verity 성능 벤치마크 방법

## 1. 기준 성능 측정 (dm-verity 없이)
$ fio --name=baseline --filename=/dev/sda1 --rw=read \
    --bs=4k --numjobs=4 --iodepth=32 --size=1G \
    --direct=1 --group_reporting
# 결과 예: IOPS=150k, BW=585MB/s (NVMe SSD)

## 2. dm-verity 성능 측정
$ fio --name=verity --filename=/dev/mapper/verified_root --rw=read \
    --bs=4k --numjobs=4 --iodepth=32 --size=1G \
    --direct=1 --group_reporting
# 결과 예: IOPS=130k, BW=507MB/s (약 13% 오버헤드)

## 3. 순차 읽기 비교 (대용량 블록)
$ fio --name=seq_base --filename=/dev/sda1 --rw=read \
    --bs=128k --numjobs=4 --iodepth=16 --size=4G \
    --direct=1 --group_reporting
# 결과 예: BW=3.2GB/s

$ fio --name=seq_verity --filename=/dev/mapper/verified_root --rw=read \
    --bs=128k --numjobs=4 --iodepth=16 --size=4G \
    --direct=1 --group_reporting
# 결과 예: BW=2.9GB/s (약 9% 오버헤드, 캐시 워밍업 후)

## 4. SHA-NI 하드웨어 가속 확인
$ grep sha_ni /proc/cpuinfo | head -1
# flags: ... sha_ni ...

# 또는 Crypto API에서 확인
$ cat /proc/crypto | grep -A5 sha256
# driver: sha256-ni (하드웨어 가속)
# driver: sha256-ssse3 (SSSE3 최적화)
# driver: sha256-generic (소프트웨어)

## 5. 해시 알고리즘별 성능 비교
# openssl speed 벤치마크
$ openssl speed sha256 sha512 blake2b512
# SHA-256 (SHA-NI): ~2.5 GB/s
# SHA-256 (소프트웨어): ~500 MB/s
# SHA-512: ~700 MB/s
# BLAKE2b: ~1.2 GB/s (소프트웨어)
시나리오4KB 랜덤 읽기128KB 순차 읽기주요 병목
dm-verity 없음 (기준)150K IOPS3.2 GB/s디바이스 한계
dm-verity SHA-256 (SHA-NI)130K IOPS (-13%)2.9 GB/s (-9%)해시 계산
dm-verity SHA-256 (소프트웨어)95K IOPS (-37%)2.1 GB/s (-34%)CPU 해시
dm-verity + check_at_most_once140K IOPS (-7%)3.0 GB/s (-6%)초기만 검증
dm-verity SHA-256 + FEC128K IOPS (-15%)2.8 GB/s (-12%)FEC 메타 읽기
# dm-verity 성능 튜닝 가이드

## 1. dm-bufio 캐시 크기 최적화
# 해시 트리 크기 계산: 4GB 데이터 / 128 팬아웃 = ~32MB 해시
# 해시 트리 전체를 캐싱하도록 설정
$ echo 67108864 | sudo tee /sys/module/dm_bufio/parameters/max_cache_size_bytes
# 64MB (해시 트리 32MB + 여유)

## 2. readahead 최적화
# dm-verity 디바이스의 readahead 확대
$ sudo blockdev --setra 4096 /dev/dm-0
# 2MB readahead (순차 읽기 성능 개선)

## 3. check_at_most_once 활성화
# dmsetup 테이블에 옵션 추가
$ echo "0 $SIZE verity 1 $DATA_DEV $HASH_DEV 4096 4096 \
    $BLOCKS 1 sha256 $ROOT_HASH $SALT \
    1 check_at_most_once" | dmsetup create verified

## 4. 해시 알고리즘 변경 (BLAKE2b로 성능 개선)
$ veritysetup format --hash blake2b-256 /dev/sda1 /dev/sda2

## 5. CPU 어피니티 조정 (kverityd 워크큐)
# kverityd 스레드를 성능 코어에 고정
$ taskset -p 0xFF $(pgrep -f kverityd)

## 6. 부팅 시 해시 트리 프리로드
# dd로 해시 디바이스 전체를 읽어 페이지 캐시에 로드
$ dd if=/dev/sda2 of=/dev/null bs=1M status=progress

## 7. 모니터링: dm-bufio 캐시 히트율 추적
$ watch -n 1 'echo "allocated: $(cat /sys/module/dm_bufio/parameters/current_allocated_bytes) \
  peak: $(cat /sys/module/dm_bufio/parameters/peak_allocated_bytes)"'
성능 튜닝 요약: dm-verity 오버헤드를 최소화하려면 (1) SHA-NI 지원 CPU 사용, (2) dm-bufio 캐시를 해시 트리 전체 크기 이상으로 설정, (3) check_at_most_once 옵션 활성화 (신뢰 환경), (4) readahead를 2MB 이상으로 설정하세요. NVMe SSD 환경에서 이 조합은 순차 읽기 오버헤드를 5% 미만으로 줄입니다.

디버깅 및 트러블슈팅

dm-verity 문제 진단에는 dmsetup status, 커널 로그, ftrace 등의 도구를 활용합니다. 검증 실패 발생 시 원인 파악과 복구 방법을 단계별로 살펴봅니다.

# 1. dm-verity 상태 확인
$ dmsetup status verified_root
# 출력 형식: start_sector num_sectors verity V C
# 0 4194304 verity V 0 C 0
# V = 검증된 블록 수, C = corruption 감지 수

# FEC 포함 상태
$ dmsetup status verified_root
# 0 4194304 verity V 1048576 C 3 F 2 U 1
# V=검증 성공, C=corruption, F=FEC 복구 성공, U=복구 실패

# 2. dm-verity 테이블 확인
$ dmsetup table verified_root
# 0 4194304 verity 1 /dev/sda1 /dev/sda2 4096 4096 ...

# 3. 커널 로그에서 verity 메시지 확인
$ dmesg | grep -i verity
# device-mapper: verity: sha256 using implementation "sha256-ni"
# device-mapper: verity: 254:0: data block 12345 is corrupted

# 4. 검증 실패 블록 위치 추적
# 커널 로그에서 손상된 블록 번호 확인
$ dmesg | grep "is corrupted"
# device-mapper: verity: 254:0: data block 12345 is corrupted
# 블록 12345 = 바이트 오프셋 50,585,600 (12345 * 4096)

# 5. 해시 수동 검증
# 특정 블록의 해시를 직접 계산하여 비교
$ dd if=/dev/sda1 bs=4096 skip=12345 count=1 2>/dev/null | sha256sum
# 실제 해시: abc123...
$ veritysetup verify /dev/sda1 /dev/sda2 ROOT_HASH
# Verification failed at position 50585600 (block 12345)

# 6. ftrace로 verity I/O 경로 추적
$ echo function_graph | sudo tee /sys/kernel/tracing/current_tracer
$ echo verity_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

# 7. perf로 성능 프로파일링
$ perf record -g -p $(pgrep -f kverityd) -- sleep 10
$ perf report --no-children
# sha256_ni_transform: 45%  (해시 계산)
# dm_bufio_read: 20%       (해시 블록 읽기)
# verity_verify_io: 15%    (검증 로직)
증상원인해결 방법
부팅 시 dm-verity 실패루트 해시 불일치올바른 루트 해시로 재설정, vbmeta 재서명
특정 파일 읽기 시 -EIO해당 블록 데이터 손상FEC 활성화 확인, 파티션 재플래싱
대량 검증 실패 (C 값 급증)디스크 물리 결함디스크 교체, SMART 상태 확인
성능 저하 (읽기 속도 느림)dm-bufio 캐시 부족max_cache_size_bytes 증가
kverityd CPU 100%SHA-NI 미지원, 소프트웨어 해싱CPU 지원 확인, BLAKE2b 전환 고려
FEC 복구 실패 (U 값 증가)에러 범위 초과roots 값 증가, 파티션 재생성
# dm-verity 트러블슈팅 스크립트

#!/bin/bash
# dm-verity 상태 종합 진단

DM_NAME="${1:-verified_root}"

echo "=== dm-verity 디바이스 정보 ==="
dmsetup info "$DM_NAME"

echo -e "\n=== 테이블 ==="
dmsetup table "$DM_NAME"

echo -e "\n=== 상태 ==="
dmsetup status "$DM_NAME"

echo -e "\n=== dm-bufio 캐시 ==="
echo "현재: $(cat /sys/module/dm_bufio/parameters/current_allocated_bytes) bytes"
echo "최대: $(cat /sys/module/dm_bufio/parameters/max_cache_size_bytes) bytes"
echo "피크: $(cat /sys/module/dm_bufio/parameters/peak_allocated_bytes) bytes"

echo -e "\n=== Crypto API 해시 드라이버 ==="
cat /proc/crypto | grep -B1 -A5 "name.*sha256"

echo -e "\n=== 커널 로그 (verity 관련) ==="
dmesg | grep -i verity | tail -20

echo -e "\n=== kverityd 워크큐 ==="
ps aux | grep kverityd
검증 실패 대응: dm-verity 검증 실패가 발생하면 모드에 따라 동작이 달라집니다. EIO 모드에서는 해당 블록 읽기만 실패하고 시스템은 계속 동작하지만, restart_on_corruption 모드에서는 시스템이 즉시 재부팅됩니다. 프로덕션(Production) 환경에서는 restart_on_corruption과 FEC를 함께 사용하여 복구 가능한 에러는 자동 정정하고, 복구 불가능한 에러에서는 재부팅하는 것이 권장됩니다.
내부 문서:
외부 참고 자료: