Cum se utilizează dm-verity pe Linux: Un ghid complet și practic

  • dm-verity verifică blocurile din mers cu un arbore hash rădăcină semnat, ancorând lanțul de încredere de boot.
  • Implementarea sa modernă combină veritysetup, systemd-veritysetup, Secure Boot și UKI pentru a proteja kernelul, initramfs și cmdline.
  • Android folosește system-as-root și AVB pentru a transmite parametrii dm-verity; politicile FEC și de reacție sporesc robustețea.
  • Rădăcina imuabilă necesită separarea datelor inscriptibile (/var, /home) și programarea actualizărilor folosind imagini sau scheme A/B.

dm-verity pe Linux

Dacă sunteți îngrijorat de integritatea sistemului dumneavoastră, dm-verity este una dintre piesele cheie ale ecosistemului Linux pentru a porni în siguranță și a detecta modificarea spațiului de stocare. A apărut ca parte a mapper-ului de dispozitive al kernelului și este acum baza pentru pornirea verificată în Android, OpenWrt și distribuțiile care doresc o securitate sporită.

Departe de a fi un concept abstract, dm-verity este configurat și utilizat cu instrumente reale precum veritysetup și systemd-veritysetupValidează blocurile din mers folosind arbori hash și poate reacționa la corupție cu politici variind de la înregistrarea evenimentului până la repornirea sau blocarea sistemului. Să aruncăm o privire mai atentă, fără a lăsa detalii neclară.

Ce este dm-verity și de ce ți-ar putea interesa

Verificarea integrității cu dm-verity

dm-verity este o țintă de mapare a dispozitivelor în kernel care verifică integritatea unui dispozitiv bloc pe măsură ce datele sunt cititeFuncționează prin calcularea și verificarea hash-urilor fiecărui bloc (de obicei 4K) în raport cu un arbore hash precalculat, de obicei folosind SHA-256.

Acest design permite Fișierele nu pot fi modificate silențios între reporniri sau în timpul execuției.Este esențial pentru extinderea lanțului de încredere la pornire către sistemul de operare, limitarea persistenței programelor malware, consolidarea politicilor de securitate și asigurarea criptării și a mecanismelor MAC în timpul pornirii.

Pe Android (începând cu versiunea 4.4) și Linux în general, Încrederea este ancorată în hash-ul rădăcinii arborelui, care este semnat și validat cu o cheie publică situată într-o locație protejată (de exemplu, pe partiția de boot sau într-o UKI semnată cu Secure Boot). Spargerea oricărui bloc ar necesita spargerea hash-ului criptografic subiacent.

Verificarea se face prin bloc și la cerere: Latența adăugată este minimă în comparație cu costul I/ODacă o verificare eșuează, kernelul returnează o eroare I/O și sistemul de fișiere pare corupt, ceea ce este de așteptat atunci când datele nu sunt fiabile. Aplicațiile pot decide dacă să continue sau nu pe baza toleranței lor la erori.

Cum funcționează intern arborele de verificare

Arborele de verificare este construit în straturi. Stratul 0 reprezintă datele brute de la dispozitiv, împărțite în blocuri de 4K; se calculează un hash SHA-256 (salted) pentru fiecare bloc. Aceste hash-uri sunt apoi concatenate pentru a forma stratul 1. Stratul 1 este apoi grupat în blocuri și rehash-uit pentru a forma stratul 2 și așa mai departe până când totul se potrivește într-un singur bloc: acel bloc, atunci când este hash-uit, produce hash-ul rădăcină.

Dacă vreun strat nu completează exact un bloc, Este completat cu zerouri până ajunge la 4K pentru a evita ambiguitatea. Dimensiunea totală a arborelui depinde de dimensiunea partiției verificate; în practică, este de obicei mai mică de 30 MB pentru partițiile de sistem tipice.

Procesul general este: alegeți o sare aleatorie, hash la 4K, calculați SHA-256 cu sare per bloc, concatenează pentru a forma niveluri, completează limita blocului cu zerouri și repetă cu nivelul anterior până când rămâne un singur hash rădăcină. Acel hash rădăcină, împreună cu sarea utilizată, alimentează tabela dm-verity și semnătura.

Versiuni de format disc și algoritm

Formatul blocurilor hash de pe disc are o versiune. Versiunea 0 a fost versiunea originală utilizată în sistemul de operare ChromiumSarea este adăugată la sfârșitul procesului de hashing, digestele sunt stocate continuu, iar restul blocului este completat cu zerouri.

La Versiunea 1 este recomandată pentru dispozitivele noiSarea este adăugată anterior hash-ului, iar fiecare digest este completat cu zerouri până la puteri de doi, îmbunătățind alinierea și robustețea. Tabelul dm-verity specifică și algoritmul (de exemplu, sha1 sau sha256), deși pentru securitatea actuală se folosește sha256.

tabelul dm-verity și parametrii esențiali

Tabelul țintă dm-verity descrie unde sunt datele, unde este arborele hash și cum se verificăCâmpuri tipice de tabel:

  • dev: dispozitivul cu datele de verificat (calea de tip /dev/sdXN sau mai mare:mai mică).
  • hash_dev: dispozitiv cu arborele hash (poate fi același; dacă da, hash_start trebuie să fie în afara intervalului verificat).
  • dimensiune_bloc_de_date: dimensiunea blocului de date în octeți (de exemplu, 4096).
  • dimensiune_bloc_hash: dimensiunea blocului hash în octeți.
  • num_data_blocks: numărul de blocuri de date verificabile.
  • bloc_hash_start: offset (în blocuri hash_block_size) față de blocul rădăcină al arborelui.
  • Algoritmulalgoritm hash (de exemplu, sha256).
  • digera: codificare hexazecimală a hash-ului blocului rădăcină (inclusiv saltul în funcție de versiunea formatului); această valoare este cea în care trebuie să aveți încredere.
  • sare: sare hexazecimală.

În plus, există parametri opționali foarte util pentru ajustarea comportamentului:

  • ignoră_corupțiaÎnregistrează blocurile corupte, dar permite continuarea citirii.
  • restart_on_corruption: repornește la detectarea corupției (nu este compatibil cu ignore_corruption și necesită suport în spațiul utilizatorului pentru a evita buclele).
  • panica_la_corupție: : provoacă panică la detectarea corupției (nu este compatibil cu versiunile anterioare).
  • restart_on_error y panic_on_erroraceleași reacții dar pentru erori I/O.
  • ignore_zero_blocks: nu verifică blocurile care sunt așteptate ca zerouri și returnează zerouri.
  • utilizare_fec_de_la_dispozitiv + fec_roots + fec_blocks + fec_startActivează Reed-Solomon (FEC) pentru recuperarea datelor atunci când verificarea eșuează; zonele de date, hash și FEC nu trebuie să se suprapună, iar dimensiunile blocurilor trebuie să se potrivească.
  • verificați_cel_mai_o datăVerifică fiecare bloc de date doar prima dată când este citit (reduce costurile suplimentare cu prețul securității în atacurile live).
  • cheie_semnalizare_hash_rădăcinăReferință la o cheie din keyring pentru validarea unei semnături PKCS7 a hash-ului rădăcină la crearea mapării (necesită o configurație adecvată a kernelului și keyring-uri de încredere).
  • try_verify_in_taskletDacă hash-urile sunt memorate în cache și dimensiunea I/O permite, verifică jumătatea inferioară pentru a reduce latența; ajustat cu /sys/module/dm_verity/parameters/use_bh_bytes per clasă I/O.

Semnătură, metadate și ancorare a încrederii

Pentru ca dm-verity să fie fiabil, Hash-ul rădăcină trebuie să fie de încredere și de obicei semnatÎn Android clasic, o cheie publică este inclusă în partiția de boot, care este verificată extern de producător; aceasta validează semnătura hash a sistemului de operare rădăcină și asigură că partiția de sistem nu a fost modificată.

Metadatele Verity adaugă structură și control al versiunilor. Blocul de metadate include un număr magic 0xb001b001 (octeți b0 01 b0 01), versiunea (în prezent 0), semnătura tabelului în PKCS1.5 (de obicei 256 octeți pentru RSA-2048), lungimea tabelului, tabelul în sine și umplutura de zerouri până la 32K.

În implementările Android, verificarea se bazează pe fs_mgr și fstabAdăugarea unei bife la intrarea corespunzătoare și plasarea cheii în /boot/verity_key. Dacă numărul magic nu este unde ar trebui să fie, verificarea se oprește pentru a evita verificarea elementului greșit.

Operațiune de pornire verificată

Protecția se află în kernel: Dacă este compromis înainte de pornirea kernelului, atacatorul păstrează controlulDe aceea, producătorii validează de obicei cu strictețe fiecare etapă: o cheie încorporată în dispozitiv verifică primul bootloader, care îl verifică pe următorul, bootloader-ul aplicației și, în final, kernel-ul.

Cu kernelul verificat, dm-verity este activat la montarea dispozitivului bloc verificatÎn loc să se execute hashing pe întregul dispozitiv (ceea ce ar fi lent și ar consuma energie), acesta este verificat bloc cu bloc pe măsură ce este accesat. O eroare provoacă o eroare I/O, iar serviciile și aplicațiile reacționează în funcție de toleranța lor: fie continuă fără acele date, fie se blochează complet.

Corecția erorilor directe (FEC)

De la Android 7.0, FEC (Reed-Solomon) este încorporat cu tehnici de intercalare pentru a reduce spațiul și a crește capacitatea de recuperare a blocurilor deteriorate. Aceasta funcționează împreună cu dm-verity: dacă o verificare eșuează, subsistemul poate încerca să o corecteze înainte de a o declara irecuperabilă.

Performanță și optimizare

Pentru a reduce impactul: Activează accelerarea SHA-2 de către NEON pe ARMv7 și extensiile SHA-2 pe ARMv8 din kernel. Ajustați parametrii read-ahead și prefetch_cluster pentru hardware-ul dvs.; verificarea per bloc adaugă de obicei puțin la costul I/O, dar aceste setări fac diferența.

Noțiuni introductive despre Linux (systemd, veritysetup) și Android

Configurarea dm-verity pe Linux și Android

Pe un Linux modern cu systemd, dm-verity permite o rădăcină doar pentru citire verificată folosind veritysetup (parte din cryptsetup), systemd-veritysetup.generator și systemd-veritysetup@.service. Se recomandă includerea Secure Boot și a unei UKI (imagine unificată a kernelului) semnate, deși nu sunt strict obligatorii.

Pregătire și compartimentare recomandată

Parte a unui sistem funcțional și ajustat. Rezervați un volum pentru arborele hash (8–10% din dimensiunea rădăcinii este de obicei suficientă) și luați în considerare separarea /home și /var dacă trebuie să scrieți. O schemă tipică include: ESP (pentru bootloader), XBOOTLDR (pentru UKI-uri), root (cu sau fără criptare), partiție VERITY și, opțional, /home și /var.

Ca rădăcină, EROFS este o alternativă foarte interesantă la ext4 sau squashfsEste conceput doar pentru citire, cu performanțe foarte bune pe memorie flash/SSD, compresie lz4 implicită și utilizat pe scară largă pe telefoanele Android cu dm-verity.

Fișiere care trebuie să fie inscriptibile

Cu root ro, unele programe se așteaptă să scrie în /etc sau în timpul inițializăriiÎl poți muta în /var/etc și poți crea un link simbolic pentru orice modificare necesară (de exemplu, conexiunile NetworkManager din /etc/NetworkManager/system-connections). Reține că systemd-journald necesită ca /etc/machine-id să existe în directorul rădăcină (nu un link simbolic) pentru a evita întreruperea pornirilor timpurii.

Pentru a afla ce schimbări în execuție, folosește dracut-overlayrootSuprapune un fișier tmpfs peste rădăcină și tot ce este scris apare în /run/overlayroot/u. Adăugați modulul la /usr/lib/dracut/modules.d/, includeți overlayroot în dracut și setați overlayroot=1 pe linia kernelului; în acest fel veți vedea ce trebuie migrat în /var.

Exemple utile: pacman și NetworkManager

În Arch, este convenabil Mută ​​baza de date Pacman în /usr/lib/pacman astfel încât rootfs să reflecte întotdeauna pachetele instalate. Apoi, redirecționează memoria cache către /var/lib/pacman și fă linkul. Pentru a schimba lista de mirror-uri fără a atinge rădăcina, mută-o în /var/etc și fă linkul oricum.

Cu NetworkManager, mută conexiunile de sistem în /var/etc/NetworkManager și link din /etc/NetworkManager/system-connections. Aceasta menține root-ul imuabil și configurația activă acolo unde ar trebui să fie posibilă scrierea.

Construcția veridicității și testarea

Dintr-o imagine live și cu totul perfect și montat în ro, creați copacul și rădăcina cu formatul de configurare VerityCând este rulat, afișează linia Root Hash, pe care o puteți salva în roothash.txt. Rulați-l pentru testare cu veritysetup, deschideți root-device root verity-device $(cat roothash.txt) și montați /dev/mapper/root.

Daca preferi, mai întâi generează arborele într-un fișier (verity.bin) și apoi scrieți-l în partiția VERITY. Setul rezultat este: imaginea rădăcină, arborele verity și hash-ul rădăcină pe care îl veți fixa la bootare.

Configurați linia kernelului

Adăugați acești parametri: systemd.verity=1, roothash=contents_of_roothash.txt, systemd.verity_root_data=ROOT-PATH (de ex. LABEL=OS) și systemd.verity_root_hash=VERITY-PATH (de ex. LABEL=VERITY). Setați systemd.verity_root_options la restart-on-corruption sau panic-on-corruption pentru politici stricte.

Alte opțiuni recomandate: ro (dacă nu folosești EROFS/squashfs), rd.emergency=reboot y rd.shell=0 (prevenirea shell-urilor neautorizate dacă pornirea eșuează) și lockdown = confidențialitate pentru a proteja memoria kernelului de acces.

Partiții suplimentare cu Verity

Nu doar rădăcina: Puteți defini alte mapări în /etc/veritytab iar systemd-veritysetup@.service le va asambla la bootare. Rețineți: este mai ușor să montați RW o partiție non-root, iar un utilizator root ar putea dezactiva Verity pe acele partiții, deci valoarea de securitate este mai mică acolo.

Securitate: Secure Boot, UKI și module semnate

dm-verity nu este o soluție miraculoasă. Semnați UKI-ul și activați Secure Boot cu propriile chei pentru a împiedica pe cineva să suprascrie kernel/initramfs/cmdline (care include hash-ul rădăcinii). Instrumente precum sbupdate-git sau sbctl ajută la menținerea intactă a imaginilor semnate și a lanțului de bootare.

Dacă activați blocarea kernelului sau verificarea semnăturii modulelor, Modulele DKMS sau cele din afara arborelui trebuie semnate sau nu se vor încărca. Luați în considerare un kernel personalizat cu suport de semnare pentru conducta dvs. (consultați modulele kernel semnate).

Criptare, TPM și contorizare

dm-verity protejează integritatea, neconfidențialitatePuteți lăsa root necriptat dacă nu conține secrete și lanțul de bootare este protejat. Dacă utilizați fișiere cheie de la root pentru a debloca alte volume, atunci este o idee bună să îl criptați.

Cu TPM 2.0, systemd-cryptenroll permite legarea cheilor la PCR-urile 0,1,5,7 (firmware, opțiuni, GPT, starea bootării securizate). Adăugați rd.luks.options=LUKS_UUID=tpm2-device=auto și asigurați-vă că includeți suportul TPM2 în initramfs. systemd-boot măsoară kernel.efi în PCR4, util pentru invalidarea cheilor dacă UKI sau linia sa de comandă se modifică.

Actualizări și modele de implementare

O rădăcină doar pentru citire verificată Nu este actualizat cu managerul de pachete în mod tradiționalIdeal este să construiești imagini noi cu instrumente precum proiectul Yocto și să le publicăm. systemd are systemd-sysupdate și systemd-repart pentru descărcarea și flasharea robustă a imaginilor.

O altă strategie este Schema A/BPăstrezi două rădăcini și două verități. Copiază rădăcina activă în rădăcina inactivă, aplică modificările și repetă veritatea. Revino la următoarea pornire. Dacă folosești UKI, nu uita să actualizezi hash-ul rădăcinii în linia cmd sau să reconstruiești UKI-ul semnat.

Pentru persistență opțională, utilizați OverlayFS pe rădăcina verificată cu upper în tmpfs sau disk. De asemenea, puteți transmite systemd.volatile=overlay pentru persistență temporară. Flatpak facilitează instalarea aplicațiilor în /var și /home fără a atinge /.

Există pachete automate (de exemplu, verity-squash-root în AUR) care construiesc o rădăcină squashfs și semnează roothash-ul cu kernel și initramfs, permițându-vă să alegeți între modul persistent sau efemer și păstrând cele mai recente fișiere rootf ca rezervă. Notă: adăugarea persistenței la o unitate rădăcină verificată are cazuri de utilizare restrânse; încercați să persistați datele aplicației pe partiții separate.

Android: sistem ca root, AVB și suprapuneri de furnizori

De la Android 10, RootFS nu mai rulează pe discul RAM și se integrează cu fișierul system.img. (system-as-root). Dispozitivele care pornesc cu Android 10 folosesc întotdeauna această schemă și necesită un ramdisk pentru dm-linear. BOARD_BUILD_SYSTEM_ROOT_IMAGE este setat la false în această compilare pentru a distinge între utilizarea unui ramdisk și activarea directă a fișierului system.img.

Android 10 încorporează partiții dinamice și o inițializare în prima etapă care activează partiția logică a sistemului; kernelul nu o mai montează direct. OTA-urile exclusive pentru sistem necesită un design system-as-root, care este obligatoriu pe dispozitivele Android 10.

În niciun A/B, păstrați recuperarea separat de bootareSpre deosebire de A/B, nu există o copie de rezervă boot_a/boot_b, așa că eliminarea recuperării în non-A/B vă poate lăsa fără modul de recuperare dacă o actualizare de boot eșuează.

Nucleul montează fișierul system.img pe /converity prin două căi: vboot 1.0 (patch-uri pentru ca kernelul să analizeze metadatele Android din /system și să derive parametrii dm-verity; linia de comandă include root=/dev/dm-0, skip_initramfs și init=/init cu dm=…) sau vboot 2.0/AVB, unde bootloader-ul integrează libavb, citește descriptorul hashtree (în vbmeta sau system), construiește parametrii și îi transmite kernel-ului în cmdline, cu suport FEC și flag-uri precum restart_on_corruption.

Cu sistem ca root, nu folosiți BOARD_ROOT_EXTRA_FOLDERS pentru folderele rădăcină specifice dispozitivului: acestea vor dispărea la flasharea unui GSI. Definiți montări specifice în /mnt/vendor/ , pe care fs_mgr le creează automat și le face referire în fstab-ul arborelui de dispozitive.

Android permite o suprapunere furnizor din /product/vendor_overlay/init va monta în /vendor subdirectoarele care îndeplinesc cerințele de context SELinux și existența /vendor/ Necesită CONFIG_OVERLAY_FS=yy, iar pe kernel-urile mai vechi, patch-ul override_creds=off.

Implementare tipică: instalează fișiere precompilate în dispozitiv/ / /suprapunere_furnizor/, adăugați-le la PRODUCT_COPY_FILES cu find-copy-subdir-files la $(TARGET_COPY_OUT_PRODUCT)/vendor_overlay, definiți contextele în file_contexts pentru etc și app (de exemplu, vendor_configs_file și vendor_app_file) și permiteți mounton pe acele contexte în init.te. Testați cu atest vfs_mgr_vendor_overlay_test în userdebug.

Depanare: mesaj de corupție dm-verity pe Android

Pe dispozitivele cu sloturi A/B, schimbați sloturile sau Intermitent vbmeta/boot fără consistență roothash Aceasta poate declanșa avertismentul: dm-verity corruption, dispozitivul dvs. nu este de încredere. Comenzi precum fastboot flash –disable-verity –disable-verification vbmeta vbmeta.img dezactivează verificarea, dar lasă sistemul fără nicio garanție de integritate.

Unele bootloader-e suportă pornire rapidă oem disable_dm_verity și opusul său, enable_dm_verity. Funcționează pe unele modele, dar nu și pe altele; și poate necesita kernel/magisk cu steaguri ajustate. Utilizați pe propriul risc: acțiunea prudentă este aliniază boot, vbmeta și system, semnați sau regenerați arborele și asigurați-vă că hash-ul rădăcină așteptat corespunde cu cel configurat.

Dacă după avertisment puteți continua să apăsați butonul de pornire/oprire, sistemul pornește, dar nu mai ai un lanț de încredere intactPentru a elimina mesajul fără a sacrifica securitatea, restaurați imaginile originale semnate sau reconstruiți/verificați vbmeta cu hashtree-ul corect, în loc să dezactivați verity.

Platformele i.MX și OpenWrt

Pe i.MX6 (de exemplu, sabresd), configurați kernelul cu suport DM_VERITY și FEC, generați arborele cu veritysetup, stocați în siguranță hash-ul rădăcinii și transmiteți parametrii corespunzători în linia cmd sau integrați prin initramfs cu systemd-veritysetup. Dacă nu utilizați dm-crypt, nu aveți nevoie de CAAM pentru verity; accentul se pune pe integritate.

În OpenWrt și în sisteme Linux încorporate cu OpenEmbedded, Există eforturi pentru a integra dm-verity și SELinux (Joburi Bootlin revizuite cu intenția de a încorpora suport). Este o potrivire naturală: routerele și echipamentele de rețea beneficiază de o adresă rădăcină imuabilă, verificată și protejată de MAC.

Construcție manuală a arborelui și metadatelor (vedere detaliată)

cryptsetup poate genera arborele pentru dvs., dar dacă preferați să înțelegeți formatul, definiția compactă a liniei din tabel include: nume de mapare, dispozitiv de date, dimensiuni bloc de date și hash, dimensiunea imaginii în blocuri, poziția hash_start (imaginea blocului + 8 dacă este concatenată), hash-ul rădăcinii și saltul. După generarea straturilor concatenate (de sus în jos, excluzând stratul 0), scrieți arborele pe disc.

Să împachetezi totul, compuneți tabelul dm-verity, semnați-l (tipic RSA-2048) și grupați semnătura+tabelul în metadate cu un antet versionat și un număr magic. Apoi, concatenează imaginea sistemului, metadatele Verity și arborele hash. În fstab, marchează fs_mgr ca fiind verify și plasează cheia publică în /boot/verity_key pentru a valida semnătura.

Optimizați cu Creșteri de viteză SHA-2 pentru procesorul tău și ajustați read-ahead/prefetch_cluster. Pe hardware-ul ARM, extensiile NEON SHA-2 (ARMv7) și SHA-2 (ARMv8) reduc semnificativ costurile de verificare.

În orice desfășurare, țineți minte că Valoarea hash a rădăcinii trebuie protejată: fie că este compilat într-un UKI semnat, în partiția de boot semnată sau validat de bootloader folosind AVB. Tot ce se întâmplă după acest punct moștenește acea încredere.

Cu toate cele de mai sus la locul lor, dm-verity devine o bază solidă pentru sisteme imuabile, mobile și integrate, care acceptă actualizări tranzacționale, suprapuneri de configurare și un model de securitate modern care reduce suprafața de atac și previne persistența fără a sacrifica performanța.

Ce este proiectul Yocto?
Articol asociat:
Ce este proiectul Yocto: Un ghid complet integrat