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äiset selitykset muuttuvat 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ä, pysyykö koko virheenkorjaus- ja käyttöönottoketju ymmärrettävänä, kun jokin epäonnistuu tuotannossa.

Viimeisessä osassa monet muodikkaat keskustelut muuttuvat oudon hiljaisiksi. Ydintyö ei ole vain käännöskoodin kirjoittamista. Kyse on ohjaimesta, joka voidaan diagnosoida, kun se toimii väärin asiakkaan koneessa, yrityskuvan sisällä, vihamielisen kolmannen osapuolen ohjelmiston vieressä tai päivitysjakson jälkeen, jota kukaan tiimistä ei halunnut korjata keskiyöllä. Toimitustodellisuudella on tässä yhtä paljon merkitystä kuin kielen semantiikkalla.

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 pitkään. Huono puhdistuslogiikka ei vain luo sotkuista prosessista poistumista. Se voi turmella 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 moraaliksi 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.

Ydintyö vahvistaa myös organisaation heikkoutta. Jos tiimi ei dokumentoi invariantteja, jos tarkistus on heikko, jos virheenkorjaustieto elää yhden henkilön päässä tai jos vapautushygieniaa käsitellään valinnaisena paperityönä, matalan tason koodi suurentaa häiriön nopeasti. Kieli ei voi täysin suojella tiimiä kaoottiselta suunnittelukulttuurilta. Se voi auttaa. Se ei voi korvata kurinalaisuutta.

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ä.

On toinenkin käytännön etu: Rust voi helpottaa rajoitetun laajuuden komponenttien luovuttamista. Jos omistusmalli on näkyvissä ja käyttöliittymät ovat kapeampia, tulevilla insinööreillä on paremmat mahdollisuudet muuttaa koodia ilman, että heidän tarvitsee omaksua heimojen historiaa vain välttääkseen sen räjähtämisen. Ytimen viereisessä työssä tällainen ylläpidettävyys ei ole akateemista. Näin tiimit pitävät kovat järjestelmät terveinä ajan mittaan.

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.

Toinen tekijä on toimintavarmuus. Monet Windows-kuljettajatiimit osaavat jo lukea kaatumisluettelot, vahvistaa symbolit, toistaa vian ja lähettää hotfix-korjauksen C++-ensimmäisessä maailmassa. Tämä toimintalihas ei ole hohdokas, mutta sen korvaaminen on kallista. Siirtyminen, joka parantaa koodin eleganssia ja heikentää tapausvastausta, ei ole nettovoitto.

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.

Tästä syystä 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.

He ovat myös yhteistyön kannattajia. Kuljettajan raja, joka on dokumentoitu tarpeeksi hyvin, jotta useat insinöörit voivat harkita sitä, alentaa sankaruusveroa. Se tekee koodin tarkistuksesta vahvemman. Se tekee tarkastuksista vähemmän teatraalista. Se tekee tulevista korjauksista vähemmän riippuvaisia ​​muistista ja enemmän riippuvaisia ​​nimenomaisesta teknisestä totuudesta. Sillä on merkitystä missä tahansa järjestelmässä, mutta sillä on merkitystä erityisesti etuoikeutetuissa ohjelmistoissa.

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 toteutustiimin 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ä tiellä, 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.

Vahvimmat ydintiimit tietävät myös, että tekninen rauhallisuus on osa toimituksen terveyttä. Kun koodi, työkalut ja sopimukset ovat luettavissa, työ pysähtyy adrenaliinista riippuen. Tämä tekee järjestelmästä turvallisemman pitkällä aikavälillä, koska tulkintapaniikin alla tehdään vähemmän muutoksia.

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.

Tämä johtuu siitä, että IOCTL-virheet eivät ole yksittäisiä virheitä. Ne saastuttavat koko käyttäjätilan ja ydintilan välisen luottamussuhteen. Kun raja on epämääräinen, jokainen loppupään päätös on vaikeampi perustella.

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++issa. Joskus se mahdollistaa tulevaisuuden Rust-komponentin. Joka tapauksessa voitto on todellinen.

Se on myös mitattavissa. Kaatumisallekirjoitukset tulevat puhtaammiksi. Arvostelu helpottuu. Vahvistuskeskustelut lyhenevät. Kun edistystä voidaan osoittaa näillä termeillä, joukkueet eivät ole niin houkuttelevia jahtaamaan dramaattista uudelleenkirjoitusta vain tunteiden sulkemiseksi.

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.

Täällä organisaatiot saavat usein myös käytännöllisimmän arvon ensin, koska paremmat työkalut parantavat jokaista tulevaa tutkimusta, jokaista käyttöönottoa ja jokaista tapahtuman jälkeistä analyysiä. Vahvemmat työkalut tekevät ydininsinööritiimistä terveemmän, mikä on todellinen järjestelmätulos, vaikka se ei koskaan näkyisikään konferenssin benchmarkissa.

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.

Pointti ei ole itse aritmeettinen temppu. Tarkoituksena on harjoitella implisiittisen rakenteen muuttamista eksplisiittiseksi suunnittelukieleksi. Monet matalan tason tuska tulevat joukkueista, jotka kiertävät koodattuja arvoja, ikään kuin jokainen luonnollisesti tietäisi, mitä ne tarkoittavat.

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.

Jos laajennat työkalua merkitsemään yleisiä menetelmiä ja käyttötapoja, alat myös rakentaa tapaa, joka yleistyy hyvin: jokaisesta läpinäkymättömästä ydinsopimuksesta tulee vähemmän vaarallinen, kun se hahmonnetaan tylsillä, selkeästi sanotuilla termeillä, joita muu tiimi voi tarkastaa.

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ä.

Tätä hyvin hoitavat tiimit eivät sekoita nykyaikaisuutta kypsyyteen. He käyttävät kieltä, työkaluja ja toimintatapoja, jotka tekevät koko järjestelmästä paremmin hallittavissa. Se on vähemmän teatraalinen lopputulos kuin laaja saarna, mutta se on myös sellainen, joka kestää kosketuksen todellisten koneiden kanssa.

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, estettyä päätöstä. Tai kirjoita suoraan osoitteeseen midgard@stofu.io.

0 / 10000
Tiedostoa ei ole valittu