[사이버 보안] dm-verity 개념과 실습(Manual)
DM-verity
Data Mapper Verity의 약자로 쓰이는데, 루트 파일 시스템(Root FS)의 검증에 쓰인다. 과거에 안드로이드에 쓰였지만 현재는 점차 임베디드에 쓰이기 시작한다. 기본 원리는 아래의 해시 트리(Hash-Tree)를 만드는 것인데, Data Block마다 Hash를 생성해서 가장 아래 level의 hash 노드를 만들기 시작하고 그 위 level의 hash 노드를 그 아래 level에 hash node를 이용하여 만들어 점차 트리를 완성시켜가는데, 이때 가장 맨 위의 Hash 노드를 Root Hash라고 하며 안전하게 보관되어져야한다. 이 Root Hash값을 이용해서 dm-verity를 검증해야하기 때문.
이러한 Hash tree를 Merkel Tree라고도 한다.
1. 블록 장치 생성 + 파일 시스템 설정
# truncate -s 10G data_partition.ext4
# mkfs -t ext4 data_partition.ext4
# mkdir mnt
# mount -o loop data_partition.ext4 mnt/
# echo "hello" > mnt/one.txt
# echo "integrity" > mnt/two.txt
# umount mnt/
# truncate -s 100M hash_partition.verity
10G의 크기를 갖는 블록을 하나 생성하여 ext4 파일 시스템을 적용한다. 그리고 그 안에 one.txt, two.txt를 만들고 난 후 unmount를 한다.
이후 100M 크기의 hash의 값을 갖는 영역을 생성해낸다. 이곳에 dm-verity의 merkle-tree가 저장된다.
2. dm-verity format
# veritysetup -v --debug format data_partition.ext4 hash_partition.verity > root_hash.txt
root_hash.txt의 내용
# cryptsetup 2.2.2 processing "veritysetup -v --debug format data_partition.img hash_partition.img"
# Running command format.
# Allocating context for crypt device hash_partition.img.
# Trying to open and read device hash_partition.img with direct-io.
# Initialising device-mapper backend library.
# Formatting device hash_partition.img as type VERITY.
# Crypto backend (OpenSSL 1.1.1f 31 Mar 2020) initialized in cryptsetup library version 2.2.2.
# Detected kernel Linux 5.13.0-52-generic x86_64.
# Setting ciphertext data device to data_partition.img.
# Trying to open and read device data_partition.img with direct-io.
# Hash creation sha256, data device data_partition.img, data blocks 2621440, hash_device hash_partition.img, offset 1.
# Using 4 hash levels.
# Data device size required: 10737418240 bytes.
# Hash device size required: 84557824 bytes.
# Updating VERITY header of size 512 on device hash_partition.img, offset 0.
VERITY header information for hash_partition.img
UUID: 4da1ecb5-5111-4922-8747-5e867036d9de
Hash type: 1
Data blocks: 2621440
Data block size: 4096
Hash block size: 4096
Hash algorithm: sha256
Salt: f2790cf141405152cf61b6eb176128ad0676b41524dd32ac39760d3be2d495cf
Root hash: a2a8fd07889deb10b4cdf53c01637ed373212cd7d0877a8aa9ae6fd4240f0f71
# Releasing crypt device hash_partition.img context.
# Releasing device-mapper backend.
# Closing read write fd for hash_partition.img.
Command successful.
Root Hash(a2a8fd07889deb10b4cdf53c01637ed373212cd7d0877a8aa9ae6fd4240f0f71)의 내용은 trusted된 내용이어야하고, 안전하게 보관되어야한다.
3. dm-verity open
# veritysetup open \
> data_partition.ext4 \
> verity-test \
> hash_partition.verity \
> a2a8fd07889deb10b4cdf53c01637ed373212cd7d0877a8aa9ae6fd4240f0f71
data_partition.ext4의 mapper를 생성한다.
/dev/mapper/verity-test 가 생성된 것을 확인할 수 있다.
# mkdir mnt
# mount /dev/mapper/verity-test mnt/
mount: /home/ubuntu/ext4/mnt: WARNING: source write-protected, mounted read-only.
# cat mnt/one.txt mnt/two.txt
hello
integrity
read-only로 mount 되었다.
4. dm-verity verify
user-space에서 검증이 가능하다.
- 검증 성공 시
# veritysetup verify \
> /dev/mapper/verity-test \
> hash_partition.verity \
> a2a8fd07889deb10b4cdf53c01637ed373212cd7d0877a8aa9ae6fd4240f0f71
#
- 검증 실패 시
hash의 값을 변경(끝 네자리 0f71 → 0f73)
# veritysetup verify \
> /dev/mapper/verity-test \
> hash_partition.verity \
> a2a8fd07889deb10b4cdf53c01637ed373212cd7d0877a8aa9ae6fd4240f0f73
Verification of root hash failed.
5. Corruption 발생 처리
dm-verity는 디스크를 binary level 수준으로 보호하기 때문에 한 비트라도 어긋나게 되면 corruption 발생 처리하며 mount되지 않는다.
disk를 read-write로 mount하는 것 만으로도 meta-data 변형이 일어나기 때문에 binary가 변경된다. 따라서 corruption을 일으키기 충분하다.
# umount mnt/
# veritysetup close verity-test
# mount -o loop data_partition.ext4 mnt/
# umount mnt/
# veritysetup open \
> data_partition.ext4 \
> verity-test \
> hash_partition.verity \
> a2a8fd07889deb10b4cdf53c01637ed373212cd7d0877a8aa9ae6fd4240f0f71
Verity device detected corruption after activation
root# mount /dev/mapper/verity-test mnt/
mount: /path/to/mnt: can't read superblock on /dev/mapper/verity-testroot## mount /dev/mapper/verity
dmesg를 확인하면 kernel에서 dm-verity의 log를 확인 할 수 있다.
[412036.212897] device-mapper: verity: 7:16: data block 0 is corrupted
[412036.212996] device-mapper: verity: 7:16: data block 0 is corrupted
[412036.213009] buffer_io_error: 91 callbacks suppressed
[412036.213011] Buffer I/O error on dev dm-0, logical block 0, async page read
[412036.223697] device-mapper: verity: 7:16: data block 0 is corrupted
5. dm-verity status
현재 mapping 상태를 확인한다.
# veritysetup status verity-test
/dev/mapper/verity-test is active.
type: VERITY
status: corrupted
hash type: 1
data block: 4096
hash block: 4096
hash name: sha256
salt: f2790cf141405152cf61b6eb176128ad0676b41524dd32ac39760d3be2d495cf
data device: /dev/loop11
data loop: /home/ubuntu/ext4/data_partition.ext4
size: 20971520 sectors
mode: readonly
hash device: /dev/loop10
hash loop: /home/ubuntu/ext4/hash_partition.verity
hash offset: 8 sectors
root hash: a2a8fd07889deb10b4cdf53c01637ed373212cd7d0877a8aa9ae6fd4240f0f71
6. dm-verity close
mapping을 제거한다.
# veritysetup close verity-test
/dev/mapper에서 사라진것을 확인할 수 있다.
Reference)
https://www.timesys.com/security/dm-verity-without-an-initramfs/
https://www.starlab.io/blog/dm-verity-in-embedded-device-security