C++, Rust ja C++: Missä turvallisuus auttaa ja rajat edelleen purevat

C++, Rust ja C++: Missä turvallisuus auttaa ja rajat edelleen purevat

C++, Rust ja C++: Missä turvallisuus auttaa ja rajat edelleen purevat

Johdanto

Windows-ydin on paikka, jossa valkotaulun puhtaat tuomiot menevät ja huomaavat, että ne ovat takaisin velkaa todellisuudelle. Tavallisessa sovellustyössä tiimillä on joskus varaa epämääräiseen selittämiseen, miksi jokin asia meni rikki. Ydintyössä epämääräisillä selityksillä on tapana muuttua virhetarkistuksiksi, sinisiksi näytöiksi, vihaisiksi operaattoreiksi ja virheenkorjausistunnoiksi, jotka saavat sinut tuntemaan, että kone on henkilökohtaisesti pettynyt kasvatukseesi.

Tästä syystä C++- ja Rust-keskustelu Windows-ytimen ympärillä on tärkeä. Ei siksi, että toinen puoli olisi nostalginen ja toinen valistunut, vaan koska matalan tason Windows-työ pakottaa kaikki väitteet selviytymään kosketuksesta IOCTL-rajojen, IRQL-sääntöjen, DMA-oletusten, synkronoinnin, elinikäisen kurinalaisuuden ja työkalujen kanssa, jotka silti odottavat sinun käyttäytyvän kuin aikuinen, vaikka arkkitehtuuripakettisi ei niin tekisi.

Rust ansaitsee vauhtinsa täällä. Muistin turvallisuus ei ole väärennös. Selkeämpi omistajuus ei ole väärennös. Selkeämmät vikapinnat eivät ole väärennettyjä. Jos kirjoitat järjestelmäkoodia lähellä etuoikeuksia ja kieli voi poistaa koko luokan helposti tehtäviä virheitä, se ei ole kosmeettista. Se on vakava insinöörietu, joka C- ja C++-tiimien on historiallisesti joutunut luomaan uudelleen kurinalaisuutta, arvostelua ja jonkin verran lievää vainoharhaisuutta.

Mutta Windows-ydin ei jaa palkintoja hyvistä aikomuksista. Se palkitsee tiimejä, jotka voivat toimia olemassa olevan ekosysteemin sisällä: WDK-rajoitukset, C-pohjaiset rajapinnat, vanhat ajurit, olemassa olevat koodikannat, WinDbg-työnkulut, ydinobjektin elinkaaren säännöt, DMA- ja synkronointitodellisuudet sekä tuskallisen tärkeä kysymys siitä, onko koko virheenkorjaus- ja käyttöönottoketju ymmärrettävissä, kun jokin epäonnistuu tuotannossa.

Joten hyödyllinen kysymys ei ole "Rust tai C++?" Hyödyllinen kysymys on tämä: missä Rust luo todellista etua, missä C++ pysyy käytännön oletusasetuksena, ja miten suunnittelet rajan niin, että järjestelmästä tulee turvallisempi sen sijaan, että se onnittelee enemmän itseään?

Miksi Windows Kernel ei ole yleisten järjestelmien leikkikenttä

Ihmiset puhuvat usein järjestelmäohjelmoinnista ikään kuin jokaisella matalan tason verkkotunnuksella olisi sama tunneilmapiiri. Se ei ole totta. Windows-ydin ei ole vain "jotkin matalan tason koodit". Se on toimintaympäristö, jossa on tiukat sopimukset ja erittäin kalliita tapoja saada selville, että olet ymmärtänyt ne väärin.

IRQL on olemassa. Lähetysreitit on olemassa. Hakurajoituksia on olemassa. Laitepinoja on olemassa. IOCTL-sopimuksia on olemassa. Synkronointivirheet eivät pysy teoreettisina kauaa. Huono puhdistuslogiikka ei vain luo sotkuista prosessista poistumista. Se voi vioittaa tilan, kiilauttaa laitteen polun tai kaataa koneen, jonka joku muu olisi halunnut jatkaa toimintaansa.

Tämä tarkoittaa, että ydin rankaisee kahta vastakkaista illuusiota. Ensimmäinen illuusio on, että kaiken matalan tason työn pitäisi pysyä C:ssä tai C++issa ikuisesti, koska maailma on aina ollut näin. Toinen illuusio on, että Rustin käyttö muuttaa teoksen automaattisesti moraaliseksi voitoksi. Molemmat ovat laiskoja tapoja välttää todellinen suunnitteluongelma.

Todellinen ongelma on muokata järjestelmä niin, että vaarallisin raja on pieni, mitattavissa, virheenkorjattavissa ja selkeästi omistettu. Joskus tämä tarkoittaa, että C++ on edelleen paras käytännöllinen istuvuus, koska ajurimalli, olemassa oleva koodi, työkalut ja tiimikokemus viittaavat siihen. Joskus se tarkoittaa, että Rust-komponentti todella alentaa riskiä ja lisää selkeyttä. Useimmiten se tarkoittaa, että vastaus on sekalainen, ja vain aikuiset ovat tyytyväisiä sekaviin vastauksiin.

Missä Rust todella auttaa Windows-matalatyössä

Rust auttaa eniten, kun se poistaa hämmennystä koodista, jolla ei ole oikeutta olla sekava. Rajojen jäsentäminen, valtion ja koneen hygienia, selkeä omistajuus, selkeämmät siivousmallit ja tiukempi kurinalaisuus sen suhteen, mikä voi olla alias tai elää kauemmin, mikä on merkityksellistä, voittaa. Ytimen viereisissä tai raskaissa järjestelmissä tällä on merkitystä, koska matalan tason virheet ovat harvoin runollisia. Ne ovat toistuvia, rakenteellisesti tuttuja ja nöyryyttäviä tavoilla, joilla insinööritiimit ovat viettäneet vuosikymmeniä teeskennelleet osana romanssia.

Rust on erityisen houkutteleva rajoitetuissa komponenteissa, joissa käyttöliittymä voidaan pitää selkeänä. Apuohjelmakerrokset, apumoduulit, tarkasti määritellyt jäsentimet, jotkin käyttäjätilan kumppanit ohjaimille, sisäiset työkalut ja huolellisesti eristetyt ytimen tai ajurin logiikan osat voivat hyötyä kielen rajoituksista, jos ympäröivä suunnittelutarina on riittävän kypsä tukemaan niitä.

Se auttaa myös kulttuurisesti. Tiimit, jotka tuovat Rust:n Windows-järjestelmäympäristöön, saavat usein terveellisempiä keskusteluja eliniästä, aliakseista, puhdistamisesta ja siitä, mitä raja tarkalleen lupaa. Siitä on hyötyä myös silloin, kun lopullinen arkkitehtuuri on hybridi. Kielet muokkaavat keskustelua, ja joskus parempi keskustelu on jo aineellista edistystä.

Mutta mikään tästä ei tarkoita, että Windows-ydin on nyt leikkipaikka, jossa joukkueiden pitäisi kirjoittaa uudelleen teologisella innostuksella. Rajoitettu voitto on edelleen rajoittunut voitto. Tämä ero on se, kuinka vakavalla suunnittelulla vältetään muodostumasta erittäin kalliiksi lifestyle-brändiksi.

Missä C++ pitää edelleen todellista maata

C++ pysyy vahvana Windows-ytimen ja ajurien toiminnassa sinnikkäästi käytännöllisistä syistä. C:n ja C++:n ympärille on rakennettu valtava määrä olemassa olevaa ohjainkoodia, näytteitä, malleja, virheenkorjaushistoriaa ja toimittajan integraatiohistoriaa. Tässä tilassa työskentelevät tiimit lähtevät harvoin tyhjästä maasta. He perivät ohjaimia, suodatinketjuja, laitesopimuksia, käyttäjätila-asiakkaita, vanhoja C++, rakennusoletuksia ja toimintatottumuksia, jotka ovat jo C++ muotoiltuja, vaikka koodi on puoli C ja emotionaalisesti 100 prosenttia velkaa.

Myös työkalutarinalla on merkitystä. WinDbg, WDK-näytteet, KMDF- ja WDM-tottumukset, kuljettaja-todentaja-työnkulut, symbolien tulkinta, kaatumisten tutkiminen ja laajempi Windows-ytimen työhön liittyvä virheenkorjauskulttuuri ovat kaikki edelleen syvällä nykyisessä alkuperäismaailmassa. Kun ryhmä on paineen alla, diagnoosin kypsyys ei ole koristeellinen etu. Näin työstä ei tule julkisesti suoritettavan arkeologian kausi.

On myös integraatioongelma. Ohjaimet elävät usein vanhan koodin, käyttäjätilan apuohjelmien, olemassa olevan asennuslogiikan, toimittajan SDKs tai tietoturvatyökalujen rinnalla, jotka on jo sidottu C- ja C++-oletuksiin. C++ ei ole automaattisesti parempi abstraktisti. Se on usein parempi välittömästi, koska ympäröivä järjestelmä on jo koulutettu puhumaan sitä.

Tämä ei mitätöi Rust. Se tarkoittaa yksinkertaisesti sitä, että todistustaakka muuttuu sen mukaan, missä komponentti sijaitsee. Uusi eristetty moduuli on yksi argumentti. Ajuripino, joka on kierretty vuosien alkuperäisten oletusten läpi, on toinen. Vakavat tiimit lakkaavat teeskentelemästä olevansa samassa tilanteessa.

Vaarallinen pinta ei katoa. Se Liikkuu.

Tämä on koko keskustelun tärkein kohta. Turvaton toiminta ei katoa, koska osa koodikannasta on kirjoitettu Rust-kielellä. Se siirtyy. Se kerääntyy FFI-reunoihin, puskurin rajoihin, synkronointisaumoihin, allokointipolkuihin, laitesopimuksiin ja paikkoihin, joissa Windows määrittelee toimintamallin edelleen itse, eikä yhden kielen hienouksien perusteella.

Siksi joukkueet voivat tehdä huonon kaupan, jos he juhlivat kieltä liian aikaisin ja rajaa liian myöhään. Rust-moduuli, joka siirtyy huolimattomasti vanhaan ohjainkoodiin, voi silti periä vanhan kaaoksen sekä uuden integraatioveron. C++-ohjain, joka eristää vaarallisen pinnansa, dokumentoi sen invariantit, pitää IOCTL-semantiikan tylsänä ja pysyy syvästi testattavana, saattaa tuottaa vähemmän kokonaisia ​​yllätyksiä kuin muodikkaampi arkkitehtuuri, joka laajensi rajoja ja kertoo hyveensä erittäin luottavaisesti.

Aikuisten suunnittelukysymys on siksi pienempi ja terävämpi. Mikä moduuli voidaan eristää? Mikä käyttöliittymä voi pysyä vakaana? Mitkä omistajuussäännöt voidaan ilmaista riittävän selkeästi, jotta kielierot eivät muodostu ajonaikaiseksi sekaannukseksi? Mikä virheenkorjauspolku toimii edelleen, kun järjestelmä on jo tulessa eikä kukaan ole mielissäni filosofisista vivahteista?

Nämä kysymykset eivät ole Rust vastaisia. He ovat selviytymisen puolesta.

Miltä Hyvä näyttää

Hyvä Windows-ytimen suunnittelu ei kuulosta sankarilliselta. Se kuulostaa rauhalliselta.

Riskillinen tie on tiedossa. IOCTL-sopimus on selkeä. Samanaikaisuustarina on tylsää parhaalla tavalla. Omistusoletukset on dokumentoitu. Crash-dump-analyysi on mahdollista. Käyttöönottosuunnitelma ei uskalla. Kuljettajan raja on riittävän kapea, jotta joku alkuperäisen toteutusryhmän ulkopuolinen voi silti ymmärtää, mikä on turvallista muuttaa ja mikä on turvallista jättää rauhaan.

Jos Rust on käytössä, pitäisi olla selvää, miksi. Sen ei pitäisi olla siellä, koska "tulevaisuus" on kirjoitettu dialle vahvalla fontilla. Sen pitäisi olla olemassa, koska määritetty komponentti todella hyötyy kielen rajoituksista ja koska tiimi voi tukea valinnasta seuraavaa virheenkorjausta, rakentamista ja käyttötarinaa.

Jos C++ pysyy kriittisellä polulla, sitä ei pidä puolustaa kohtalona. Sitä tulisi puolustaa todisteilla: työkalujen kypsyys, integrointikustannukset, kuljettajien rajoitteet, tiimikokemus ja mitattu näkemys siitä, mistä epävakaus todellisuudessa johtuisi, jos komponentti siirrettäisiin. C++ pitäisi olla suunnittelussa, koska se ansaitsi istuimen, ei siksi, että järjestelmä oli liian väsynyt väittämään.

Käytännön tapaukset, jotka kannattaa ratkaista ensin

IOCTL-rajojen puhdistus

Monet kuljettajat vaativat järjestelmät ovat vähemmän vaarassa niiden älykkäin koodin kuin huolimattomien sopimusrajojen vuoksi. IOCTL-käsittelyn, validoinnin, rakenteen versioinnin ja käyttäjien välisten oletusten puhdistaminen tuottaa usein turvallisempia tuloksia nopeammin kuin kunnianhimoiset uudelleenkirjoitukset.

Kapea ohjainytimen karkaisu

Pieni kuljettajaydin, jossa on selkeät invariantit ja parempi omistajuuskuri, on yleensä arvokkaampi kuin valtava teoreettinen siirto. Joskus tämä kovettuminen tapahtuu C++:ssa. Joskus se mahdollistaa tulevaisuuden Rust-komponentin. Joka tapauksessa voitto on todellinen.

Käyttäjätilan kumppanit ja työkalut

Täällä Rust loistaa usein ilman draamaa. Diagnostiikkatyökalut, toisto-apuohjelmat, konfigurointivalidaattorit, sieppausanalysaattorit tai ohjatut apuprosessit voivat tulla selkeämmiksi ja turvallisemmiksi ilman, että haurainta ydinpolkua vedetään uuteen integraatiouskontoon ennen kuin järjestelmä on valmis.

Käytännön laboratorio: Pura Windows IOCTL tylsällä tavalla

Windows-ydin rankaisee ryhmiä, jotka käsittelevät ohjauskoodeja koristeellisten kokonaislukujen tavoin. Rakennetaan pieni apuohjelma, joka purkaa IOCTL-arvon, jotta raja lakkaa olemasta mystinen.

main.cpp

#include <cstdint>
#include <iomanip>
#include <iostream>

struct IoctlParts {
    std::uint32_t device_type;
    std::uint32_t access;
    std::uint32_t function;
    std::uint32_t method;
};

IoctlParts decode_ioctl(std::uint32_t code) {
    return IoctlParts{
        (code >> 16) & 0xFFFFu,
        (code >> 14) & 0x3u,
        (code >> 2) & 0x0FFFu,
        code & 0x3u
    };
}

int main() {
    constexpr std::uint32_t ioctl = 0x222004;
    const auto parts = decode_ioctl(ioctl);

    std::cout << "IOCTL 0x" << std::hex << std::uppercase << ioctl << "\n";
    std::cout << "device_type=0x" << parts.device_type << "\n";
    std::cout << "access=0x" << parts.access << "\n";
    std::cout << "function=0x" << parts.function << "\n";
    std::cout << "method=0x" << parts.method << "\n";
}

Rakentaa

Windows ja MSVC:

cl /O2 /std:c++20 main.cpp
.\main.exe

Linux- tai macOS-käyttöjärjestelmässä, jossa on monialustainen kääntäjä:

g++ -O2 -std=c++20 -o ioctl_decode main.cpp
./ioctl_decode

Mitä tämä opettaa sinulle

Pointti ei ole aritmetiikka. Asia on siinä, että matalan tason Windows työskentely helpottuu heti, kun piilotettua rakennetta ei enää käsitellä taianomaisena. Pura raja, nimeä kentät, tee sopimus näkyväksi, ja yhtäkkiä virheenkorjauskeskustelu lyhenee ja vähemmän uskonnollista.

Testitehtävät harrastajille

  1. Luo sama dekooderi uudelleen Rust:ssa ja vertaa koodin pituuden lisäksi myös ohjaimen työkaluketjun muuhun osaan paljastamasi rajan selkeyttä.
  2. Laajenna dekooderi tulostamaan ihmisen luettavat nimet METHOD_BUFFERED, METHOD_IN_DIRECT ja niihin liittyville arvoille.
  3. Lisää jäsentäjä IOCTL-koodien luettelolle tekstitiedostosta ja lajittele ne laitetyypin ja toiminnon mukaan.
  4. Luo pieni fuzz-syötejoukko satunnaisista IOCTL-arvoista ja varmista, että dekooderisi pysyy vakaana ja tylsänä.
  5. Lisää yksi tarkoituksella huolimaton raja-oletus ja tarkasta sitten, kuinka nopeasti "vaaraton" pikakuvake muuttaa koko työkalun valehtelijaksi.

Yhteenveto

Rust on todellinen parannus Windowsin matalan tason suunnittelussa, kun sitä käytetään hämmennyksen kaventamiseen, omistajuuden selkeyttämiseen ja tiettyjen vältettävissä olevien virheiden kutistamiseen. C++ on edelleen todellinen ja usein perusteltu oletusarvo, kun työ on sidottu olemassa oleviin ohjaimiin, olemassa oleviin työkaluihin, olemassa olevaan virheenkorjauskulttuuriin ja toimintapolkuihin, jotka elävät edelleen vahvasti alkuperäisessä ekosysteemissä.

Todellinen tehtävä ei ole valita moraalista voittajaa. Todellinen tehtävä on suunnitella rajoja, jotka pysyvät ymmärrettävissä, kun järjestelmä on jo paineen alla. Ydintyössä tämä on ero suunnittelun ja optimismin välillä.

Viitteet

  1. Windows-ohjaindokumentaatio: Windows
  2. I/O-ohjauskoodien määrittäminen: https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/defining-i-o-control-codes
  3. Laitteiston prioriteettien ja IRQL:n hallinta: https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/managing-hardware-priorities
  4. WDF-ohjaimen kehityskatsaus: https://learn.microsoft.com/en-us/windows-hardware/drivers/wdf/
  5. Windows-virheenkorjaustyökalujen dokumentaatio: Windows
  6. Epäturvallinen Rust: Rust
Philip P.

Philip P. – CTO

Takaisin Blogeihin

Ota yhteyttä

Aloita keskustelu

Muutama selkeä viiva riittää. Kuvaile järjestelmää, painetta ja päätöstä, joka on estetty. Tai kirjoita suoraan osoitteeseen midgard@stofu.io.

01 Mitä järjestelmä tekee
02 Mikä nyt sattuu
03 Mikä päätös on estetty
04 Valinnainen: lokit, tiedot, jäljet, erot
0 / 10000
Tiedostoa ei ole valittu