( uid_2716 | 2019. 11. 21., cs – 23:38 )

Ez a jelenség elméletileg bármilyen UEFI platformon előfordulhat. Az operációs rendszer a SetVariable() UEFI runtime service-t meg tudja hívni -- ez így van tervezve (ld. UEFI spec). Ha a módosítás tárgyát egy non-volatile változó képezi, akkor az a módosítás az alaplapi flash-ben fog tárolódni (vagy -- elméletileg -- bármilyen más tartós tárban, amely túléli, ha az alaplap teljes áramellátása megszűnik).

A változó módosításának konkrét hatása attól függ, hogy a változó mire való. A UEFI spec definiál egy bizonyos namespace GUID-ot, és azon belül néhány (nem kevés) változót, amelyek hatása pontosan specifikált. Bármely platform vendor generálhat magának tetszőleges számú namespace GUID-ot, másokkal való koordináció nélkül (ld. uuidgen), azon belül pedig úgy nevez el változókat, és arra használja azokat, ahogy neki tetszik. Ilyen változókba OS alól csak akkor érdemes belepiszkálni -- eltekintve mondjuk a kifejezetten erre szánt vendor utility-ktől --, ha pontos dokumentációnk van a firmware-ről. A vendor ugyanis nem feltétlenül szánja az adott változót végfelhasználói felületnek, és nem feltétlenül végez sanity check-eket a változó tartalmán. Ha szemetet írunk bele, abból lehet a firmware-ben buffer overflow, integer overflow, vagy kontrollált, de nekünk nem tetsző viselkedés.

Az efibootmgr utility-t bizonyára sokan ismerik és használják: na, az pont ilyen változó-manipulációt végez, csak éppen standard (a UEFI spec-ből származó) változókon dolgozik (BootOrder, Boot####, Timeout, BootNext, stb; a namespace GUID pedig EFI_GLOBAL_VARIABLE: 8BE4DF61-93CA-11D2-AA0D-00E098032B8C).

A baj az, hogy általában nincs semmilyen eszköz vagy szoftver, ami a fenti szolgáltatás alatt helyezkedene el. Az alatt névutó megvilágításához gondoljunk például egy diszkre (HDD, SSD, USB pendrive, mindegy). Tegyük fel, hogy vírusos lesz, vagy eltolunk valamit a bootloader konfigban vagy bootloader bináris(ok)ban, és a  gép egyszerűen nem indul. Nincs semmi veszve, rádugjuk egy másik gépre a diszket (vagy a gépet beindítjuk másik boot media-ról), és az eredeti diszket "adatszinten" legyaluljuk. Innentől újra van egy használható, újratelepíthető diszkünk. Ez azért működik, mert a diszk leválasztható, és van szoftver (nevezetesen egy másik OS), amit a diszk elrontott állapota alá be tudunk dugni. (Ebben a példában eltekintek attól, hogy a diszkvezérlőn is lehet flash...)

Na, ez az alsóbb szintű szoftver v. eszköz az, ami általában nincs az alaplapi flash-hez, vagy legalábbis nem feltétlenül szabványosan. Létezik JTAG (meg van sok firmware security előadás / prezentáció, amelyben flash-t olvasnak ki, meg fejtenek vissza, lásd pl. itt), de ez mind platformfüggő, és idő- és szakértelem-igényes. Ha könnyen hozzáférünk is a hardverhez, még mindig ott van a probléma, hogy nem feltétlenül tudjuk, milyen UEFI változóval toltuk el a dolgot, milyen módosítást kellene visszavonni. A diszkes példára visszatérve, nem feltétlenül állunk neki a partíciós táblát vagy a boot sectort reszelgetni, hanem gyakran letakarítjuk az egészet (úgyis van backup :)) -- és sajnos ez a "letakarítjuk az egész flash-t" az, ami egy végfelhasználóknak szánt platformon elérhetetlen.

A konkrét esetben a szerviz számára is egyszerűbb volt az egész alaplapot kicserélni (csak azért, hogy a flash ismert és működőképes állapotba kerüljön!), mint

  • vagy kicserélni csak a flash-t az alaplapon,
  • vagy a flash (ill. a benne élő non-volatile változók) tartalmát hardveres eszközökkel megváltoztatni (és persze mire?).

Még az edk2 (EFI Development Kit II) projektben is, mely a UEFI és a PI (Platform Init) specifikációk nyílt forrású referencia implementációja, többször előfordult olyan patch, amelynek hatására egy korábbi firmware verzió által eltárolt UEFI variable tartalmat az új firmware verzió már nem tudott visszaolvasni. Bizonyos esetekben ez implementációs hiba volt (például annak nyilvántartásában, hogy egy adott hálózati kártyához, PXE vagy HTTP(S) netboot céljára, hogyan rendelünk IP címet: sehogyan, statikusan, vagy DHCP-vel). A legfrissebb ezzel kapcsolatos hibajegy, amelyről tudok, itt van. Más esetben pedig egyenesen UEFI spec szintű inkompatibilitás kúszott be (pl. a SATA device path node-ok reprezentációja megváltozott, így egy firmware frissítés hatására korábbról meglévő UEFI boot opciók érvénytelenné válhattak).

QEMU/KVM virtuális gépen OVMF firmware-t futtatva (mely egy edk2 platform) ez a fajta probléma jól demonstrálható. Nagy különbség fizikai platformokhoz képest, hogy egy virtuális gép alatt mindig van egy szoftveres réteg: a VMM őrzi ill. kezeli a guest számára a variable store-t. (Pl. QEMU/KVM + OVMF esetében egy normál host-oldali állományban.) Így a variable store file-t mindig újra lehet formázni a guest-en kívülről, a guest alatt. (Pl. QEMU/KVM + OVMF esetében simán letörlöd a korrupt v. használhatatlanná vált varstore-t, leállított guest mellett, a guest következő indításánál pedig a libvirt daemon újra létrehozza a varstore-t a varstore template-ből.)

Nyilván az is egy lehetőség, hogy a platform vendor megpróbálja előre felvértezni a változóit valamiféle kompatibilitással; itt egy példa az OVMF-ből.

Összefoglalva, a probléma gyökere az, hogy a UEFI variable store-hoz nincsen olyan szabványos felhasználói felületű eszköz -- akár szoftveres, akár hardveres --, amellyel a varstore ("flash") teljes összezagyválódása után is vissza lehetne állítani az eredeti gyári tartalmat.