dm-verity 구현

Android 4.4 이상은 블록 장치의 투명한 무결성 검사를 제공하는 선택적 device-mapper-verity(dm-verity) 커널 기능을 통해 자체 검사 부팅을 지원합니다. dm-verity는 루트 권한을 보유하고 장치를 손상시킬 수 있는 영구 루트킷을 방지하는 데 도움이 됩니다. 이 기능은 Android 사용자가 기기를 부팅할 때 기기가 마지막으로 사용되었을 때와 같은 상태인지 확인하는 데 도움이 됩니다.

루트 권한이 있는 PHA(잠재적으로 유해한 응용 프로그램)는 탐지 프로그램에서 숨기거나 스스로를 가릴 수 있습니다. 루팅 소프트웨어는 종종 탐지기보다 권한이 더 높기 때문에 이 작업을 수행할 수 있으므로 소프트웨어가 탐지 프로그램에 "거짓말"을 할 수 있습니다.

dm-verity 기능을 사용하면 파일 시스템의 기본 스토리지 계층인 블록 장치를 보고 예상 구성과 일치하는지 확인할 수 있습니다. 암호화 해시 트리를 사용하여 이 작업을 수행합니다. 모든 블록(일반적으로 4k)에는 SHA256 해시가 있습니다.

해시 값은 페이지 트리에 저장되기 때문에 최상위 "루트" 해시만 신뢰하여 트리의 나머지 부분을 확인해야 합니다. 블록을 수정하는 기능은 암호화 해시를 깨는 것과 같습니다. 이 구조에 대한 설명은 다음 다이어그램을 참조하십시오.

dm-verity-해시 테이블

그림 1. dm-verity 해시 테이블

공개 키는 부팅 파티션에 포함되어 있으며 장치 제조업체에서 외부적으로 확인해야 합니다. 해당 키는 해당 해시에 대한 서명을 확인하고 장치의 시스템 파티션이 보호되고 변경되지 않았는지 확인하는 데 사용됩니다.

작업

dm-verity 보호는 커널에 있습니다. 따라서 커널이 실행되기 전에 루팅 소프트웨어가 시스템을 손상시키면 해당 액세스 권한이 유지됩니다. 이러한 위험을 완화하기 위해 대부분의 제조업체는 장치에 구운 키를 사용하여 커널을 확인합니다. 장치가 공장에서 출고되면 해당 키는 변경할 수 없습니다.

제조업체는 이 키를 사용하여 첫 번째 수준 부트로더에서 서명을 확인하고, 이 키를 사용하여 후속 수준, 응용 프로그램 부트로더 및 최종적으로는 커널에서 서명을 확인합니다. 검증된 부팅 을 활용하려는 각 제조업체는 커널의 무결성을 검증하는 방법을 가지고 있어야 합니다. 커널이 검증되었다고 가정하면, 커널은 블록 장치를 보고 마운트될 때 검증할 수 있습니다.

블록 장치를 확인하는 한 가지 방법은 해당 내용을 직접 해시하고 저장된 값과 비교하는 것입니다. 그러나 전체 블록 장치를 확인하려는 시도는 시간이 오래 걸리고 장치의 전력을 많이 소모할 수 있습니다. 장치는 부팅하는 데 오랜 시간이 걸리고 사용 전에 상당히 소모됩니다.

대신 dm-verity는 각 블록에 액세스할 때만 개별적으로 블록을 확인합니다. 메모리로 읽을 때 블록은 병렬로 해시됩니다. 그런 다음 해시가 트리에서 확인됩니다. 그리고 블록을 읽는 것은 비용이 많이 드는 작업이기 때문에 이 블록 수준 검증으로 인해 발생하는 대기 시간은 비교적 명목입니다.

확인에 실패하면 장치에서 블록을 읽을 수 없음을 나타내는 I/O 오류를 생성합니다. 예상대로 파일 시스템이 손상된 것처럼 보입니다.

응용 프로그램은 결과 데이터가 응용 프로그램의 기본 기능에 필요하지 않은 경우와 같이 결과 데이터 없이 진행하도록 선택할 수 있습니다. 그러나 데이터 없이 응용 프로그램을 계속할 수 없으면 실패합니다.

순방향 오류 수정

Android 7.0 이상은 FEC(순방향 오류 수정)를 통해 dm-verity 견고성을 개선합니다. AOSP 구현은 일반적인 Reed-Solomon 오류 수정 코드로 시작하고 인터리빙이라는 기술을 적용하여 공간 오버헤드를 줄이고 복구할 수 있는 손상된 블록의 수를 늘립니다. FEC에 대한 자세한 내용은 오류 수정과 함께 엄격하게 시행된 자체 검사 부팅을 참조하십시오.

구현

요약

  1. ext4 시스템 이미지를 생성합니다.
  2. 해당 이미지에 대한 해시 트리를 생성합니다 .
  3. 해당 해시 트리에 대한 dm-verity 테이블을 작성하십시오 .
  4. 해당 dm-verity 테이블 에 서명하여 테이블 서명을 생성합니다.
  5. 테이블 서명 과 dm-verity 테이블을 verity 메타데이터로 묶습니다.
  6. 시스템 이미지, Verity 메타데이터 및 해시 트리를 연결합니다.

해시 트리 및 dm-verity 테이블에 대한 자세한 설명은The Chromium Projects - Verified Boot 를 참조하십시오.

해시 트리 생성

서론에서 설명한 것처럼 해시 트리는 dm-verity에 필수적입니다. cryptsetup 도구가 해시 트리를 생성합니다. 또는 호환되는 항목이 여기에 정의되어 있습니다.

<your block device name> <your block device name> <block size> <block size> <image size in blocks> <image size in blocks + 8> <root hash> <salt>

해시를 형성하기 위해 시스템 이미지는 레이어 0에서 SHA256 해시가 할당된 4k 블록으로 분할됩니다. 레이어 1은 해당 SHA256 해시만 4k 블록으로 결합하여 형성되므로 훨씬 더 작은 이미지가 생성됩니다. 레이어 2는 레이어 1의 SHA256 해시와 동일하게 형성됩니다.

이것은 이전 레이어의 SHA256 해시가 단일 블록에 들어갈 수 있을 때까지 수행됩니다. 해당 블록의 SHA256을 가져오면 트리의 루트 해시가 있습니다.

해시 트리의 크기(및 해당 디스크 공간 사용량)는 확인된 파티션의 크기에 따라 다릅니다. 실제로 해시 트리의 크기는 작은 경향이 있으며 종종 30MB 미만입니다.

이전 레이어의 해시로 완전히 채워지지 않은 레이어의 블록이 있는 경우 예상한 4k를 달성하려면 블록을 0으로 채워야 합니다. 이를 통해 해시 트리가 제거되지 않고 대신 빈 데이터로 완료되었음을 알 수 있습니다.

해시 트리를 생성하려면 계층 2 해시를 계층 1의 해시에 연결하고 계층 3의 해시를 계층 2의 해시와 연결하는 식입니다. 이 모든 것을 디스크에 기록하십시오. 이것은 루트 해시의 레이어 0을 참조하지 않습니다.

요약하자면 해시 트리를 구성하는 일반적인 알고리즘은 다음과 같습니다.

  1. 임의의 소금(16진수 인코딩)을 선택합니다.
  2. 시스템 이미지를 4k 블록으로 분리합니다.
  3. 각 블록에 대해 (소금) SHA256 해시를 가져옵니다.
  4. 이러한 해시를 연결하여 레벨을 형성합니다.
  5. 4k 블록 경계에 0으로 레벨을 채웁니다.
  6. 레벨을 해시 트리에 연결합니다.
  7. 단일 해시만 있을 때까지 이전 수준을 다음 소스로 사용하여 2-6단계를 반복합니다.

그 결과 루트 해시인 단일 해시가 생성됩니다. 이것과 소금은 dm-verity 매핑 테이블을 구성하는 동안 사용됩니다.

dm-verity 매핑 테이블 작성

커널의 블록 장치(또는 대상)와 해시 트리의 위치(동일한 값)를 식별하는 dm-verity 매핑 테이블을 작성합니다. 이 매핑은 fstab 생성 및 부팅에 사용됩니다. 이 테이블은 또한 블록의 크기와 해시 트리의 시작 위치인 hash_start(특히 이미지 시작 부분의 블록 번호)를 식별합니다.

Verity 대상 매핑 ​​테이블 필드에 대한 자세한 설명은 cryptsetup 을 참조하십시오.

dm-verity 테이블 서명

dm-verity 테이블에 서명하여 테이블 서명을 생성합니다. 파티션을 확인할 때 먼저 테이블 서명의 유효성을 검사합니다. 이것은 고정된 위치에 있는 부팅 이미지의 키에 대해 수행됩니다. 키는 일반적으로 고정 위치의 장치에 자동으로 포함되도록 제조업체의 빌드 시스템에 포함됩니다.

이 서명과 키 조합으로 파티션을 확인하려면:

  1. /verity_key/boot 파티션에 libmincrypt 호환 형식의 RSA-2048 키를 추가합니다. 해시 트리를 확인하는 데 사용되는 키의 위치를 ​​식별합니다.
  2. 관련 항목의 fstab에서 fs_mgr 플래그에 verify 를 추가합니다.

테이블 서명을 메타데이터에 번들링

테이블 서명과 dm-verity 테이블을 verity 메타데이터로 묶습니다. 메타데이터의 전체 블록은 버전이 지정되므로 두 번째 종류의 서명을 추가하거나 일부 순서를 변경하는 등 확장될 수 있습니다.

온전성 검사로서 매직 넘버는 테이블을 식별하는 데 도움이 되는 각 테이블 메타데이터 세트와 연결됩니다. 길이는 ext4 시스템 이미지 헤더에 포함되어 있으므로 데이터 자체의 내용을 모른 채 메타데이터를 검색할 수 있는 방법을 제공합니다.

이것은 확인되지 않은 파티션을 확인하도록 선택하지 않았는지 확인합니다. 그렇다면 이 매직 넘버가 없으면 확인 프로세스가 중지됩니다. 이 숫자는 다음과 유사합니다.
0xb001b001

16진수의 바이트 값은 다음과 같습니다.

  • 첫 번째 바이트 = b0
  • 두 번째 바이트 = 01
  • 세 번째 바이트 = b0
  • 네 번째 바이트 = 01

다음 다이어그램은 Verity 메타데이터의 분석을 보여줍니다.

<magic number>|<version>|<signature>|<table length>|<table>|<padding>
\-------------------------------------------------------------------/
\----------------------------------------------------------/   |
                            |                                  |
                            |                                 32K
                       block content

그리고 이 표는 해당 메타데이터 필드를 설명합니다.

표 1. Verity 메타데이터 필드

필드 목적 크기
매직넘버 fs_mgr에서 온전성 검사로 사용 4 바이트 0xb001b001
버전 메타데이터 블록의 버전을 지정하는 데 사용됩니다. 4 바이트 현재 0
서명 PKCS1.5 패딩 형식의 테이블 서명 256바이트
테이블 길이 dm-verity 테이블의 길이(바이트) 4 바이트
테이블 앞서 설명한 dm-verity 테이블 테이블 길이 바이트
이 구조는 길이가 32k로 0으로 채워집니다. 0

dm-verity 최적화

dm-verity에서 최상의 성능을 얻으려면 다음을 수행해야 합니다.

  • 커널에서 ARMv7용 NEON SHA-2와 ARMv8용 SHA-2 확장을 켭니다.
  • 다양한 미리 읽기 및 prefetch_cluster 설정을 실험하여 장치에 가장 적합한 구성을 찾습니다.