← Blog

· 13 min čtení #rust#mqtt#iot#unikernel#open source#security

urMQTT v0.3.0: šifrování disku, boot, který odmítne upravený obraz, a to, co jsme si sami pokazili

Co se stalo po třetím textu o urMQTT: šifrování disku v klidu se změřenou cenou, test, který změní jeden bajt v jádře a ukáže, že ho zavaděč odmítne, auditní balíček s 889 zdůvodněnými místy s unsafe, chyby, které jsme našli sami (a jedno tvrzení, které jsme napsali špatně), a vydání v0.3.0 se seznamem toho, co ještě není prokázáno, včetně červeného CI.

Třetí text, urMQTT po VPS, skončil seznamem „Co bude dál": veřejný demo broker (D1), druhé kolo pro pomalý jediný tok, obyčejná cesta proti Mosquittu na VPS a „po vydání" bare metal, IOMMU, Secure Boot a šifrování disku. Tento text říká, co z toho je hotovo, co ne a co jsme mezitím našli. Šifrování disku a část Secure Bootu se nakonec do vydání dostaly, protože ležely na cestě k auditu. Druhé kolo pro jediný tok a obyčejná cesta proti Mosquittu hotové nejsou.

Poznámka předem zůstává stejná: projekt je pre-alpha, vydání cílí na VPS s KVM a čísla měří kód, testy a konkrétní pokusy, ne zralost v produkci.

Jak to vypadalo v čase

Od minulého textu (1. listopadu ve 20:41) do dneška (3. listopadu ve 02:43) přibylo v hlavní větvi 37 nových commitů, celkem 215. Jako dřív se projekt staví s Claude Code (commity nesou Co-Authored-By: Claude) a tentokrát běžely paralelně tři větve (D1, H8, příprava vydání), takže pořadí commitů není přesně pořadí práce. Čísla jsou spočítána stejnou metodou jako v minulém textu (všechny soubory .rs mimo vendor/ a fuzz/, funkce #[test], nadpisy ## ADR- v docs/DECISIONS.md) a ověřena na starém commitu, který dává stejná čísla jako tehdy.

Okamžik Commit Čas Řádků Rustu Testů ADR Commitů
minulý text d21e3ec 1. 11., 20:41 112 408 926 126 178
dnes f1a7fbc 3. 11., 02:43 115 148 962 133 215

Růst kódu je skromný (+2 740 řádků, +36 testů), protože tentokrát nebylo mnoho nového kódu: většinu práce tvoří testy, skripty, dokumentace a kontroly. Skripty a JavaScript se do počtu řádků nezapočítávají.

Šifrování disku v klidu (S7)

Co jsme plánovali. Volitelné šifrování datového oddílu, ve výchozím stavu vypnuté, se změřenou cenou. Co jsme postavili (ADR-240):

  • Vrstva pod diskem. CryptDev obaluje blokové zařízení pod Disk, takže rozložení, tabulka velikostí, SQLite, telemetrie a zálohy pracují s čistým textem a o šifrování nevědí. Algoritmus je AES-256-XTS (IEEE 1619), jeden sektor o 512 bajtech na datovou jednotku, ne blok o 4 KiB, jak počítal plán: úložiště zapisuje po sektorech a jednotka 4 KiB by každý zápis 512 bajtů proměnila v čtení-úpravu-zápis, který může výpadek proudu přetrhnout napůl.
  • Superblok zůstává otevřený. Dva sloty superbloku nesou příznak a kontrolu klíče (HMAC-SHA-256 ze štítku a UUID disku), nikdy klíč. Všechno ostatní v oddílu je šifrované. Sektor, který je na médiu celý nulový, se čte jako nula a nedešifruje se, takže discard, nikdy nezapsaný prostor a zvětšování disku fungují jako dřív.
  • Konfigurace. storage.encryption = "aes-256-xts" a storage.disk_key (soubor s 64 bajty klíče). Formát blobu se stává 4 jen když je šifrování zapnuté, takže všechny ostatní obrazy se dál spouštějí na starších jádrech. Starší čtečka neznámý příznak v superbloku odmítne.
  • Bez hardwarového AES: odmítnutí. Jádro odmítne storage.encryption na procesoru bez instrukcí AES (nebo když je [cpu] disable vypne), místo aby tiše běželo na pomalé softwarové verzi.
  • Převod je offline. Jádro při startu nikdy disk nešifruje ani nedešifruje. Dělají to urmqtt-disk encrypt, decrypt a rekey při vypnutém VM: záznam o převodu se zapisuje jako první a maže jako poslední (dokud existuje, jádro disk odmítá), kousky po 1 MiB se nejdřív uloží do žurnálu na hostiteli, pak se zapíše převedená kopie a superblok se přepíná jako poslední. Vyzkoušeno výpadkem proudu po každém jednotlivém zápisu u všech tří postupů, čistě i napůl zapsaném, se zahozenou i zachovanou cache: po --resume vždy platný disk se všemi daty.

Změřeno. Propustnost XTS na jádro na Apple silicon (jeden MiB sektorů ve smyčce): 3 109 MB/s šifrování a 3 103 MB/s dešifrování s hardwarovým AES, oproti 252 a 258 MB/s v softwaru. Proto software nepovolujeme. db-bench ve VM (aarch64, HVF, 5 000 vložení v 50 transakcích, pět běhů každé varianty, střídavě):

op/s, pět běhů medián
šifrování vypnuto 49 239, 47 401, 50 802, 53 417, 46 531 49 239
šifrování zapnuto 57 790, 46 173, 45 939, 52 787, 49 632 49 632

Rozdíl mediánů je +0,8 % a rozptyl uvnitř jedné varianty (46 000 až 58 000) je větší, takže cena v tomto testu není měřitelná. To je také očekávané: celý běh db-bench zapíše asi 6 MB, což je asi 2 ms AES, a commit čeká na flush, ne na šifru.

Diagnostická položka nyní vedle AES-GCM zobrazuje i AES-XTS (na HVF jádro naměří asi 800 MiB/s pro XTS oproti asi 560 MiB/s pro AES-GCM) a ukazuje, zda je dostupný hardwarový AES.

Diagnostická položka spuštěná ve webovém emulátoru na aldo.tech, v kartě prohlížeče: v panelu CRYPTO poslední řádek ukazuje AES-XTS a „hw AES". Čísla popisují emulátor (6 MiB/s), ne urMQTT.
Diagnostická položka spuštěná ve webovém emulátoru na aldo.tech, v kartě prohlížeče: v panelu CRYPTO poslední řádek ukazuje AES-XTS a „hw AES". Čísla popisují emulátor (6 MiB/s), ne urMQTT.

Co XTS nedává. Není tu autentizace: upravený šifrovaný sektor se dešifruje na šum v blocích po 16 bajtech (CRC a čistič to najdou) a ten, kdo může zapisovat na svazek, může mezi dvěma snímky zaměnit nebo vrátit celé sektory. Chrání ukradený svazek nebo snímek, ne ukradené ISO, protože klíč je v konfiguraci obrazu. Viditelné zůstává: GPT, superblok, UUID a které sektory kdy byly zapsány.

Co jsme při tom našli ve vlastní práci. Při psaní kapitoly o kryptografii jsme zjistili dvě věci. Krok násobení tweaku v XTS měl větvení závislé na bitu odvozeném z klíče; nyní je bez větvení. A tvrzení „ověřeno podle vektoru 10 z IEEE 1619" pokrývalo jen první šifrovaný blok; nyní existuje i naivní referenční implementace (aritmetika u128, blok po bloku), která ověřuje celé sektory v obou směrech na 200 náhodných klíčích a sektorech, a dokumentace říká, co bylo skutečně ověřeno.

Boot, který odmítne upravený obraz (H8)

Požadavek z auditního seznamu byl jednoduchý a dosud neověřený: žádný test neukázal, že se upravené jádro nespustí. Pin hashe v limine.conf existoval, ale bez důkazu.

  • Test, který mění bajt. scripts/tamper.py otočí jeden bit v každé kopii jádra (nebo konfiguračního modulu) uvnitř sestaveného ISO nebo raw obrazu a SMOKE_ONLY=integrity ukáže, že zavaděč odmítne: 24 kontrol prošlo, na aarch64 UEFI, x86_64 UEFI a x86_64 BIOS, s kontrolami, které se spouštějí. Zpráva o odmítnutí se čte ze sériového portu přes přepínač určený jen pro testy. Na BIOSu zpráva dorazí jen na obrazovku, takže tento test tvrdí jen to, že se stroj nespustí.
  • Zapsaná konfigurace zavaděče (ADR-250). limine enroll-config funguje na BOOTAA64.EFI, BOOTX64.EFI i na stupni BIOS, takže se odmítne i upravený limine.conf (přidaná položka nabídky nebo argument). urmqtt-config pack --iso ji zapíše znovu.
  • Cesta Secure Bootu (ADR-251). scripts/sign-loaders.sh (zkušební klíče, podepisování, ověřování, úložiště proměnných OVMF), mkiso.sh --sb-key a skupina secureboot na x86_64: podepsané ISO se spustí a sestaví reprodukovatelně, zatímco nepodepsané, podepsané špatným klíčem a upravené po podpisu dostanou Access Denied (10 kontrol prošlo). Limine i pod Secure Bootem upravené jádro odmítne.
  • Pravidla instalační položky (ADR-253). Plán mluvil o jednom slovu na příkazové řádce, install. Produkční jádro ve skutečnosti přijímá tři (install, live, diag) a zapisuje jen install. Test (9 kontrol) to spouští na třech strojích s prázdným diskem, jehož hash se nesmí změnit.
  • Tajnost obrazu (ADR-252). Hlasité varování, když se produkční obraz staví mimo CI, a prohledání obrazu, logů sestavení a sériových logů na bajty klíčů v mnoha kódováních (11 kontrol).
  • TPM, jen ADR-254. Co by znamenalo zapečetit klíč serveru do vTPM nebo TPM 2.0. Doporučení: nestavět pro vydání zaměřené na VPS. Neověřovali jsme, kteří poskytovatelé vTPM vůbec nabízejí.

Co to NEZNAMENÁ (zapsáno jako H8-W1 až H8-W6 v AUDIT.md): nainstalovaný disk se pod Secure Bootem nespustí, protože jeho konfigurační modul nemá hash (ADR-217); podepsané ISO nelze dodatečně konfigurovat (nejdřív konfigurovat, pak podepsat); cesta BIOS nemá podpis; není tu ochrana proti návratu ke starší verzi; na aarch64 firmware zde Secure Boot nevynucuje, takže byl ověřen jen podpis zavaděče; neexistuje projektový klíč, takže obrazy vydání nejsou pro Secure Boot podepsané; a žádný fyzický stroj nebyl vyzkoušen.

Auditní balíček (H10)

docs/AUDIT.md je nyní balíček, který může externí auditor použít bez dotazů: systém na jedné stránce, základ, kterému věříme (89 612 řádků vlastního Rustu v obrazu, 67 přilinkovaných cizích balíčků), vstupy a jejich parsery, matice ochran s testy (i ochrany bez testů), slabiny se stavem, tabulka každé síťově dosažitelné smyčky a alokace s mezí a testem, návod k sestavení a testování, navržený rozsah externího auditu v deseti oblastech a red-team seznam s 31 útoky a výsledky. SECURITY.md má tabulku STRIDE po komponentách.

  • Oprava prvního textu. V textu z 29. října stojí, že každý blok unsafe má komentář // SAFETY:, který kontroluje lint. To nebyla pravda: lint (deny(unsafe_code)) jen omezuje, kde smí unsafe být, a komentáře od H0 eviduje generovaný soupis, který už tehdy našel asi 30 míst bez zdůvodnění a při auditu jich bylo 108 (protože mezitím přibyly nové druhy míst, například atributy #[unsafe(no_mangle)] a link_section). Proto je to soupis, a ne lint, co je teď drží na nule.
  • 889 míst unsafe, každé s písemným zdůvodněním. Těch 108, která ho neměla (hlavně #[unsafe(no_mangle)] a link_section, několik unsafe fn), dostalo komentář (napsal ho AI agent, jen komentáře, vzorek jsem prošel já); generovaný soupis v CI zaznamená každé nové místo bez zdůvodnění. Zdůvodnění je tvrzení, které je třeba přezkoumat, ne důkaz, a to je bod 5 navrženého rozsahu.
  • Pět otevřených nálezů zůstává otevřených, zapsáno: není fuzzing parserů PCI a virtio, není test, který zapisuje do stránkových tabulek po jejich uzamčení, doba běhu P-256, X25519 a Ed25519 nebyla měřena, není fuzzing záměrně vytvořených souborů SQLite (omezeno H6) a 53 z 67 přilinkovaných balíčků jsou výjimky cargo-vet, ne audity.

Chyby, které jsme našli sami

  • Tajemství TLS provozu se nemazala (H0-W2, otevřeno od prvního dne auditu): tabulka zachycených tajemství byla Vec, který remove a swap_remove přesouvaly a nechávaly po sobě kopie. Nyní je tabulka po slotech: slot se maže na místě, když se vezme, zahodí nebo vytlačí, nic se nepřesouvá.
  • Větvení závislé na klíči v XTS (viz výše).
  • log-slow-flood na x86_64 padal („log ring full"), i na zdrojovém kódu v0.2.1. Příčinou nebyla chyba, ale dimenzování testu: scénář nutil každé jádro na 25 ms, zatímco ADR-090 ho měřil na 50 ms. Na x86_64 je COM1 port-I/O po bajtu zachycené do pasti, jedno jádro zapisující do sinků potřebuje asi 9 ms na řádek a tři jádra na 25 ms žádají víc než sekundu práce za sekundu, takže se kruhy plní (zamýšlené přetečení). Test nyní na x86_64 běží na 50 ms (prošel, 30 z 30 v běhu se základními scénáři); na aarch64 zůstává 25 ms.
  • Sloučení větví rozbilo staré šablony. Opětovný zápis konfigurace v H8 vyžadoval koncovou značku v limine.conf, kterou šablony před H8 nemají, takže urmqtt-config pack --iso a stránka sestavovače odmítaly každé vydání před H8, včetně v0.2.x, které stránka nabízí. Zachytily to testy sestavovače v prohlížeči (4 ze 100 selhaly), ne vlastní test H8. Opraveno (šablona bez značky se konfiguruje jako dřív) a přidán test, který se stejnou původní chybou padá, když se oprava odstraní.
  • Naše dokumentace chybně tvrdila, že x86_64 bylo ověřeno jen v emulátoru. X11 spustil produkční obraz na skutečném Contabo KVM VPS (4 vCPU AMD EPYC) s oboustranným TLS pod zátěží. Opraveno všude: x86_64 běželo na skutečném KVM VPS u commitu H7 a v emulátoru, ale kód přidaný po něm (šifrování, integrita bootu) na KVM neběžel a na fyzickém hardwaru nebyl vyzkoušen.

D1 a nabídka „vaše služba jako unikernel"

Veřejný demo broker je postavený, ale nezveřejněný: konfigurace (live režim, ACL demo/{id}/#, obyčejné MQTT jen pro most), skript, který vytváří zveřejněnou zkušební CA a demo certifikáty, most WebSocket-na-TCP (jen standardní knihovna Pythonu, omezení spojení, rychlosti a velikosti, kontrola původu), stránka „Vyzkoušejte urMQTT živě" s MQTT klientem v obyčejném JavaScriptu, runbook a model hrozeb. Testováno na loopbacku a v QEMU (18 scénářů, 26 testů mostu). Netestováno: skutečný VPS, DNS, Caddy, obraz kontejneru. Potřebuje samostatný VPS a výslovný souhlas s veřejným vystavením.

Na webu je navíc zveřejněna i nabídka, Unikernel (15 jazyků, bez cen): úprava vaší služby tak, aby běžela jako unikernel (posouzení proveditelnosti, úprava, pilot, podpora), s urMQTT jako referencí. Text výslovně uvádí omezení dnešního základu: IPv4, TCP jen jako server, KVM, Rust přirozený, C jen jako izolovaný worker.

Vydání v0.3.0

Tag v0.3.0 byl nastaven 3. listopadu na commit f1a7fbc a workflow vydání uspěl. Vydání je označeno jako pre-release a obsahuje vývojové jádro pro aarch64, ISO a obraz, šablonu ISO (jádra pro aarch64 a x86_64 a prázdnou konfiguraci, kterou urmqtt-config pack --iso a stránka sestavovače promění ve váš obraz), CycloneDX SBOM, SHA256SUMS a jeho podpis minisign a atestace původu SLSA. Ověřeno po zveřejnění, v prázdném adresáři: podpis SHA256SUMS platí, všech pět souborů odpovídá kontrolním součtům, obě atestace (jádro a ISO s predikátem CycloneDX) projdou, SBOM má 221 komponent a šablona se konfiguruje (pack --iso znovu zapíše šest kopií zavaděče). Podpisový klíč je stále zkušební klíč.

Pořadí bylo důležité a stojí za zapsání: stránka sestavovače byla aktualizována před tagem (zná formát 4 a všechny starší šablony) a teprve potom si zrcadlo na aldo.tech vydání převzalo. Zrcadlo ho uvedlo jako podepsané a ověřené (ok, formát konfigurace 4, sha256 šablony shodný s tím v SHA256SUMS) a živá stránka ho nabízí jako výchozí vydání a v prohlížeči vytvoří ISO asi za 20 sekund. Poznámky k vydání říkají i to, co se píše zřídka: CI na GitHubu není zelené (viz níže), podpora je jen pro poslední verzi (v0.2.1 a starší končí) a audit provedl sám projekt.

Na webu jsme si přitom málem sami uškodili: publikační skript webu (publish.sh) dělá rsync --delete a zrcadlo vydání patří jinému uživateli, takže každá publikace skončila chybou 23 (a rozbila by tlačítko „Publikovat" v administraci blogu). Opraveno výjimkou pro /urmqtt/releases/. Podobný nález: deploy.sh s --delete maže stránku sestavovače z generátoru, takže se musí před publikací vrátit.

Co ještě není prokázáno

  • CI na GitHubu je červené. Nejen dnes: poslední běh úplného CI před tagem měl sedm neúspěšných jobů a běhy zpětně až do 31. října jsou většinou červené. Selhaly: lint (varování shellcheck ve skriptech), TLS interop (všechny případy wolfSSL), dodavatelský řetězec (seznam výjimek vet-exemptions.md není na Linuxu aktuální), fuzz smoke (v tom jobu chybí vývojové certifikáty), sanitizery (LeakSanitizer hlásí únik v testech), testy na Linuxu aarch64 (jeden test nástroje padá s „Broken pipe") a jeden důkaz Kani pro časové kolo (timer wheel) neprojde. Zatím jsme netřídili, co z toho je prostředí a co skutečná vada; dokud to neuděláme, netvrdíme, že vydání prošlo CI.
  • Podpisový klíč je zkušební, a obrazy vydání jsou vývojové se zkušebními certifikáty (kromě šablony).
  • Překlady stránek a tohoto textu do dalších 13 jazyků napsala AI a rodilí mluvčí je nezkontrolovali.
  • Šifrování disku nebylo změřeno na x86_64 (ani na KVM, ani na fyzickém hardwaru) a nebyla změřena ani propustnost telemetrie, ani doba startu se šifrováním. Na stránce sestavovače není (potřebuje dialog pro klíč).
  • Nezávislý bezpečnostní audit neexistuje. Rozsah je napsán; auditora nemáme.
  • Secure Boot je částečný (viz výše), bez fyzického stroje.
  • Bare metal, IOMMU, veřejný demo broker: nehotovo nebo nezveřejněno.

Co bude dál

  1. Roztřídit a opravit CI (nejdřív prostředí, pak důkaz Kani a test, který padá na Linuxu aarch64), protože vydání bez zeleného CI nepovažujeme za hotové.
  2. Rozhodnutí o D1 (samostatný VPS, DNS, souhlas s veřejným vystavením).
  3. Fuzzing parserů PCI a virtio a test uzamčených stránkových tabulek (H0-W9, H0-W10).
  4. Dialog pro klíč na stránce sestavovače, aby se šifrování stalo dostupným bez příkazové řádky.
  5. Po vydání: bare metal (X12 až X16) a IOMMU, v pořadí, které závisí na soupisu hardwaru testovacího PC.

Kód je na github.com/zeljan-alduk/urMQTT; docs/PROGRESS.md zůstává jediným zdrojem pravdy o tom, co skutečně funguje, docs/PERFORMANCE.md o tom, co bylo změřeno, a docs/AUDIT.md o tom, co ještě není prokázáno. Pre-alpha, jako dřív; hlášení chyb a bezpečnostní hlášení podle pokynů v SECURITY.md jsou vítána.

Komentáře

Pravidla komentování

Pravidla komentování

Komentáře slouží ke sdílení zkušeností a otázek k tématům blogu. Tato pravidla udržují diskuzi užitečnou pro všechny.

  1. Slušnost Žádné urážky, výhrůžky, obtěžování, nenávistné projevy ani diskriminace z jakéhokoli důvodu. Kritizujte argument, ne osobu.
  2. K tématu Komentář se má týkat příspěvku nebo diskuze pod ním.
  3. Bez reklamy a spamu Žádná inzerce, řetězové zprávy ani odkazy bez kontextu. Odkazy a obrázky jsou vítány, když diskuzi obohacují.
  4. Soukromí ostatních Nezveřejňujte osobní údaje jiných lidí: celá jména soukromých osob, adresy, telefony, e-maily, fotografie bez souhlasu.
  5. Autorská práva Zveřejňujte jen obsah, který smíte sdílet. Při citaci uveďte zdroj.
  6. Jedna identita Používejte jednu přezdívku a nevydávejte se za někoho jiného.
  7. Potvrzení e-mailem Komentář se zveřejní až po potvrzení odkazu zaslaného e-mailem. Adresa se nezobrazuje a k ničemu jinému se nepoužívá; nepotvrzené komentáře se po 48 hodinách mažou.
  8. Moderace Komentáře porušující pravidla skrýváme nebo mažeme bez upozornění a diskuzi můžeme uzamknout. Rozhodnutí moderátorů jsou konečná.
  9. Odpovědnost Za obsah komentáře odpovídá jeho autor. Komentáře nevyjadřují názory ALDO TECH.
  10. Nahlášení Nevhodný obsah nahlaste na info@aldo.tech.