Tämä kurssi on jo päättynyt.

Käyttöjärjestelmien perusteita

Jos kyse ei ole ihmisen aivoissa tapahtuvista suojautumisista tai harhautumisista, tärkein tekijä kyberturvallisuuden toteutumisessa tai murtamisessa lienee käyttöjärjestelmä, joka on eräänlainen keskushermosto kaikenlaisissa tietojenkäsittelylaitteissa. Tässä pääluvussa käsitellään käyttöjärjestelmiin liittyviä uhkia ja suojautumista, mutta aluksi on tiivis esittely piirteistä, joita käyttöjärjestelmistä on syytä ymmärtää. Esittelyssä viitataan motivaation vuoksi jo moniin turvaseikkoihin, joihin palataan myöhemmin.

Aivan aluksi on syytä määritellä kaksi käsitettä:

Suojausalue (security domain):

ajossa olevan koodin kokonaisuudesta sellainen osa, jonka sisällä vallitsee sama tietoturvapolitiikka eikä tarvitse epäillä hyökkäyksiä. Tyypillisesti käyttöjärjestelmän ydinosat (kernel) on yksi alue, kunkin yhden käyttäjän prosessit muodostavat toisen, välissä olevat käyttöjärjestelmän osat ehkä kolmannen. Suojausalueet suhtautuvat toisiinsa epäillen ja tarkistuksia tehden tai ehkä mahdollisimman täysin eristettyinä, joskin kerneliä kaikki tietysti käyttävät ja siihen yleensä luottavat.

Hypervisor

Kun laitteisto toteutetaan ohjelmallisesti, sitä sanotaan virtuaalikoneeksi ja todellisen laitteiston kanssa on tekemisissä hypervisor eli virtuaalikoneen monitori (virtual machine monitor). Voi olla myös niin että hypervisor ei ole laitteen vaan isäntäkäyttöjärjestelmän päällä. Koska hypervisor on kuitenkin paljolti käyttöjärjestelmän kaltainen (se on emulaattori), siihen viitataan vain joissain kohdin, kun jotain tärkeää eroa on.

Käyttöjärjestelmien olemuksesta

Tietokoneen käyttöjärjestelmä (KJ, operating system, OS) on nimensä mukaan systeemi, jolla laitetta voidaan käyttää, operoida. Ohjelma voisi käyttää laitetta toki ilmankin KJ:tä, esim. KJ itse on sellainen ohjelma. Yksi KJ:n olemassaolon tarkoituksia on tehdä sen päällä ajettavat sovellukset enemmän tai vähemmän riippumattomiksi kulloisestakin laitteistokokoonpanosta. On ilmeistä, että erityyppisten laitteiden yhteydessä tarvitaan melko erilaisia KJ:tä ja laitteen käyttäjällä on enemmän tai vähemmän mahdollisuuksia muokata KJ:n rakennetta tai ominaisuuksia:

keskustietokone – palvelinkone – monen prosessorin laite – henkilökohtainen tietokone – mobiililaite – reaaliaikajärjestelmä – sulautettu järjestelmä – toimikortti –
hajautettu järjestelmä (jossa on verkko-KJ) – virtuaalikone (eli alla onkin toinen KJ, esim. hypervisor)

Käyttöjärjestelmän ja laitteiston välissä voi olla “ohut” kerros laitteistokohtaista ohjelmistoa, nimittäin HAL, hardware abstraction layer. Tämä kerros samoin kuin mahdollinen mikro-ohjelmointi suorittimen varsinaisen käskykannan käyttäjänä jätetään tällä kurssilla huomiotta. Pohjimmaltaan KJ:n kautta käytettävät laitteet ovat:

  • Suoritin, CPU, jossa on erityisesti kokoelma rekistereitä ja niiden joukossa ohjelmalaskuri (PC, program counter), joka osoittaa seuraavaksi suoritettavaan käskyyn.
  • Keskusmuisti (RAM)
  • Oheismuisti (tai massamuisti), joka liittyy laiteohjaukseenkin, mutta jossa erityisesti käännetään loogisia (ja virtuaalisia) osoitteita fyysisiksi.
  • Laiteohjaimet (device controllers), jotka välittävät CPU:n ja I/O-laitteiden vuorovaikutuksen. Tässä kohdassa tulee mukaan myös ulkoinen tietoliikenne.
  • Väylät: kukin yhdistää yleensä useita tietokoneen osia. Keskusyksikön sisäisten väylien lisäksi voi olla erilaisia laiteväyliä.

Sikäli kuin nämä ovat toistensa yhteydessä, niitä voidaan kutsua nimellä keskusyksikkö, erotuksena oheislaitteista, esim. tulostimesta. Kaikkia laitteita hyödyntää käyttäjä, yleensä sovellusohjelmiensa luomien prosessien kautta. Ne ovat varsinaisia KJ:n käyttäjiä. Tavalliselle ohjelmien käyttäjälle, ihmiselle, näkyy yleensä vain KJ:n komentotulkki, joka sekin on vain sovellusohjelma, olipa se komentorivipohjainen tai graafinen eli ikkunoitu. Käyttöliittymän nimitys shell eli kuori on kuvaava. Käyttäjän asemassa on usein jokin automaattisesti käynnistetty prosessi, esimerkiksi web-palvelin.

Sovellusten ohjelmoijan täytyy tietää muistakin kuin laitteisiin kohdistuvista liitynnöistä. Ohjelmat käyttävät KJ:tä systeemikutsujen avulla. Tarjolla olevien kutsujen kokoelmasta käytetään nimeä API-liittymä tai -rajapinta (application programming interface). Se toteuttaa siis laitteiston abstraktion ja luo ohjelmoijalle ikään kuin koneen, jota hän käyttää. Se voi jakautua eri rajapintoihin keskusyksikköä, oheislaitteita ja verkkoyhteyksiä varten. (Termi API tarkoittaa myös sitä rajapintaa, jolla ohjelmoijat liittävät koodiaan toisten tekemiin ohjelmistoihin.)

Tietoturvan kannalta hankaluutena on se, että tunkeutuja tai haittaohjelman kirjoittaja voi toimia paljon matalammalla tasolla kuin tavallinen käyttäjä. Yksi tunnettu hyökkäyskohde on ollut käynnistyssektori.

Kun tietokone käynnistetään, suoritin alkaa käsitellä ohjelmakäskyjä ROM-muistista (ja nykyään yleensä flash-muistista). Niiden avulla tarkistetaan, että keskusyksikkö ja ROM:lla niinikään sijaitseva BIOS toimivat odotetulla tavalla (“itsetesti”). Tulos tallennetaan.

Tämän jälkeen BIOS (laitelmisto, firmware, basic input output system) alkaa herätellä massamuisteja ja hakee sieltä käynnistyssektorilta käyttöjärjestelmän perusosan, alkulataajan (bootstrap loader). Se on pieni ohjelma, jonka tehtävänä on pohjustaa ympäristöä käyttöjärjestelmälle ja ladata se muistiin. Pohjustus tarkoittaa sopivassa järjestyksessä tapahtuvaa KJ:n tietorakenteiden pystyttämistä ja myös tarpeellisten ajuriohjelmien lataamista. (Koska KJ siis tavallaan lataa KJ:n, kyseessä on sama ilmiö kuin paroni von Münchausenin pelastautuminen suosta saapasnauhoista vetämällä–siis boottaus). Tämän jälkeen kontrolli siirtyy KJ:lle. Ennen KJ:n käynnistymistä tehtävät tarkistukset voivat olla varsin mutkikkaita (vrt. TPM, trusted platform module).

Tässä materiaalissa tarjotaan hyvin karkea johdanto käyttöjärjestelmiin ja moni asia tulee esille vasta kun siihen liittyvää turvallisuutta käsitellään. Sopivia johdantotekstejä löytyy verkosta tarkemman näkemyksen hankkimista varten. Kokonainen kirjakin on tarjolla “OSs: Three Easy Pieces”. Aiheeseen liittyy paljon muutakin kuin turvallisuuden käsittely edellyttää ja KJ on varsin monimutkainen ohjelmisto. Siitä voi vakuuttua tarkastelemalla Linux-ytimen tehtävittäin ja kerroksittain jäsennettyä kaaviota.

Mitä tekemistä ohjelmalaskurilla (program counter) on käyttöjärjestelmän (KJ) kanssa?

Käyttöjärjestelmä erottelijana ja yhdistäjänä

Jotta vaativat tietoturvapolitiikat voisivat toteutua, tietokonelaitteistolta voidaan edellyttää peukaloinnin sieto-ominaisuuksia ja ainakin osan käyttöjärjestelmästä pitää olla erityisen luotettava. Tässä tarkastellaan laitteiston ja sitä käyttävän järjestelmän perusosia vain “tavanomaisten” vaatimusten puitteissa ja keskitytään niissäkin ohjelmien suoritukseen liittyviin turvaseikkoihin. Keskeistä siinä on erottelu. Tiivistäen sanottuna käyttäjät eli heidän prosessinsa pidetään riittävästi erossa toisistaan (luottamuksellisuus ja eheys) sekä käyttöjärjestelmästä ja laitteistosta (eheys).

Tämä on tarpeen silloinkin, kun käyttäjä täysin omistaa laitteen. Laitteen asetukset ja käyttöjärjestelmän rakenteet ovat siinä määrin monimutkaisia, että vahingossa tapahtuva muuntelu voi aiheuttaa tietojen vaarantumisen tavalla tai toisella — myös siten, että käsittely katkeaa (saatavuus). Saatavuuteen liittyy moni käyttöjärjestelmän rakenne, mutta kyse on silloin lähinnä tehokkuudesta ja reiluudesta eivätkä nämä seikat kuulu tavanomaisissa tilanteissa tietoturvan piiriin. Kriittisissä, reaaliaikaisissa, sovelluksissa käyttöjärjestelmän pitää huolehtia siitä, että jotkin prosessit saavat prosessoriaikaa aina, kun ne sitä tarvitsevat.

Tärkeitä KJ:n perusrakenteita em. yleisessä katsannossa ovat seuraavat. Hakasuluissa on mainittu kokonaisuuden hahmottamiseksi asioita, jotka liittyvät käyttöjärjestelmien turvatehtäviin mutta ovat hieman perusrakenteita etäämpänä laitteistosta.

  • prosessit: etuoikeudet, keskeytykset, säikeet, rinnakkaisuus, vuoronnus. [käyttäjien tunnukset, ryhmät, autentikointi, kirjanpito]
  • muisti: erilaiset muistit (ROM, RAM…), osoiterakenne, viittaustekniikat, virtuaalimuisti.
  • tiedostot ja oheislaitteet. [pääsyoikeudet, hakupolut, karsinointi, “mounttaus” eli resurssiksi liittäminen]

Käyttöjärjestelmän erottelutehtävän taustalla on nykyaikaisen KJ:n tarve huolehtia lukuisista usean toimijan yhteiskäytössä olevista resursseista. Periaatteessa tapoja ovat seuraavat:

  • Peräkkäin tietty kokonaisuus kerrallaan: tulostimet, nauhatkin vielä joskus.
  • Peräkkäin keskeytysmekanismin avulla: prosessit CPU:ssa; Yksi erityishuoli on lukkiutumisen (deadlock) estäminen. Vuorontaja (scheduler, dispatcher) puolestaan yrittää pitää huolta suorituskyvystä ja reilusta jaosta niin, ettei mikään prosessi nälkiinny.
  • Rinnakkain: prosessien muistivaraus ja tiedostot, myös mahdollinen rinnakkaisten suorittimien hallinta. Eri toimijoiden erottelun lisäksi KJ:n täytyy hallinnoida muisti- ja levytilaa pirstomatta sitä liian pieniin paloihin, joita ei voida käyttää (tai joiden käyttö hidastaisi suoritusta liikaa).
  • Porrastetusti tai liukuhihnamaisesti: prosessorin sisäisen toteutuksen tapaan, joka ei ole käyttöjärjestelmän asia. Ennakoiva suorittaminen on lähellä tätä, ks. hyökkääjämallien kohdalta.

Vain ensimmäinen on tiukka erottelu, eikä sekään siinä mielessä kuin monitasoisen turvallisuuden mekanismeissa edellytettäisiin (MLS). Prosessien peräkkäisessä suorituksessakin voi olla tarpeen varmistua rekistereiden nollaamisesta. Yhteisellä alueella olevien tietorakenteiden käytön voisi ajatella tapahtuvan “päällekkäin”. Silti käyttöjärjestelmän pitää lukitusten avulla huolehtia poissulkemisesta niin, että sekin sujuu peräkkäin ilman törmäyksiä. Tämä tarkoittaa mm., että vanhentunutta tietoa ei kirjoiteta uudemman päälle.

Erotteluista huolimatta prosessien välillä voi tapahtua tiedonvaihtoa monella tavalla. Yhtenä hallinnollisena tavoitteena on samaa työtä tekevien prosessien synkronointi. Yleisesti keinoja ovat

  • tiedostot
  • muisti (saman prosessin säikeiden välillä sekä segmentoinnissa).
  • signaalit, jotka välittyvät prosessille sen perustietoja kuvaavan prosessielementin kautta. Näillä voidaan prosessi esim. lopettaa.
  • semaforit, erityiset tietorakenteet, joiden tehtävänä on toteuttaa poissulkeminen.
  • sanomanvälitys eri mekanismeilla: - postilaatikon tapaan - jonojen avulla (esim. putket ja nimetyt putket) - sanomakanavan kautta (esim. soketit)

Edellä on eri kohdissa tullut mainituksi neljä tärkeintä rinnakkain etenevien toistensa kanssa tekemisissä olevien prosessien haastetta käyttöjärjestelmälle: poissulkeminen, synkronointi, lukkiutuminen ja nälkiintyminen. Nämä asiat ovat em. kirjan toisena “helppona palana” eli otsikon Concurrency alla.

Edellä perusrakenteiden luettelossa hakasulkuihin merkityt tulevat esille tuonnempana lukuun ottamatta karsinointia. Se tarkoittaa, että prosessin näkymä tiedostojärjestelmään rajoitetaan tiettyyn hakemistoon ja sen alihakemistoon. Mikä seuraavista Linux-komennoista on tätä varten? Tulkintaa varten sinun täytyy tietää, että ch=change, acl=access control list, grp=group, mod=mode, own=owner, root=root.
Tietoturvatavoite saatavuus tulee melko vähän esille käyttöjärjestelmän myöhemmissä luvuissa, ja selityskin mainittiin edellä. Saatavuus kuitenkin liitetään tässä muutamaan asiaan, muihin paitsi yhteen näistä; eli mihin ei? (Tässä silti ‘checkbox’ eikä ‘radio button’, jotta saat palautetta myös oikeasta.)

Prosesseja käyttöjärjestelmissä

Sovellukset, joita koneen suorittimen avulla ajetaan, muodostuvat yleensä useista prosesseista. Lisäksi on lukuisia prosesseja, joilla käyttöjärjestelmä (KJ) suorittaa hallinnointia eli erityisesti antaa prosesseille ja sitä kautta kullekin sovellukselle riittävästi suoritusaikaa, jotta nämä voivat toimia tarkoituksenmukaisesti. Tämä pyritään tekemään niin, että suorittimen ajasta mahdollisimman suuri osa kuluu muuhun eli sovellusten prosesseihin. Hallinnon yleiskuormaa (‘overhead’) aiheuttaa erityisesti ajettavien prosessien vaihtaminen ja monet vaihtamiseen liittyvät siirrot ja tarkistukset.

Prosessi voi olla etuoikeutettu tai käyttäjän prosessi tai sen suojaustila (moodi) voi joissain järjestelmissä olla jotain siltä väliltä. Erottelu voidaan tehdä laitteiston tarjoamien suojausbittien perusteella; esim. jo Intelin historiallisessa 80386/486:ssa on kaksi bittiä, joista saadaan neljä moodia eli suojakehää (protection ring). Käyttäjän prosessit ovat uloimmalla kehällä ja saavat etuoikeutettujen prosessien palveluja kutsumalla niitä aliohjelmien tapaan. Muut kehät voivat olla sisältä lukien ydin, käyttöjärjestelmän muut osat sekä varusohjelmat (utilities). Tiloilla saadaan aikaan laitteistoläheistä monitasoista turvallisuutta. Vastaavasti monitahoista turvallisuutta edistää se, että prosessit toimivat omissa osoiteavaruuksissaan ja kommunikoivat keskenään vain KJ:n tarjoamien palveluiden kautta.

Sekä Unix että Windows käyttävät mainituista suojakehistä vain sisintä ja ulointa, Windowsissa “kernel mode” ja “user mode”, Unixissa 0 ja 3. Windowsissa ympäristön vaihdot (context switch) sekä siirtymät näiden kehien välillä toteuttaa Local Procedure Call facility, ja käyttäjän ohjelmat kutsuvat käyttöjärjestelmää API-liittymien kautta. Yksi perustava turvallisuuteen liittyvä mekanismi Windowsissa on rinnakkaisuutta hallinnoiva lukitus, jolla käyttäjä voi estää toisten pääsyn objektiin.

Saman prosessin säikeet (threads, “kevytprosessit”) jakavat keskenään saman osoiteavaruuden. Sen ansiosta niiden suoritusten välinen vuorottelu säästää vaivaa, mutta toisaalta KJ ei voi soveltaa turvamekanismeja niiden välille vastaavalla tavalla kuin prosesseille.

Keskeytys (interruption) tarkoittaa, että ajossa oleva prosessi asetetaan joksikin aikaa syrjään ja tehdään jotain muuta, joka sillä hetkellä on tärkeämpää (esim. jota ohjelma ei “itse” osaa tehdä) tai “tasapuolisempaa” (kun esim. ajetaan välillä jonkun toisen käyttäjän prosessia). Keskeytyksiä on monenlaisia:

  • ohjelmistokeskeytykset, jotka ajettava prosessi aiheuttaa itse:
    • ohjelmoidut: prosessi kutsuu KJ:tä. Systeemikutsuja tarvitaan erityisesti I/O:ta eli siirräntää varten, jolloin prosessi käyttää jotain oheislaitetta.
    • ohjelmavirheet (esim. nollalla jakaminen, virheellinen muistiviittaus)
  • ulkoiset keskeytykset
    • kello, esim. 100 kertaa sekunnissa.
    • I/O-keskeytykset, jotka syntyvät, kun siirräntä päättyy. Tällöinhän jonkin prosessin status pitää muuttaa valmiustilaan.

Keskeytyksillä on hierarkia, jota voidaan ajatella myös turvarakenteena: alemman tason keskeytys ei keskeytä ylemmän tason käsittelyä. Ulkoisia keskeytyksiä voidaan lisäksi tilapäisesti ehkäistä asettamalla peite (mask), jotta KJ pystyy antamaan aikaa jollekin tärkeämmälle prosessille. On silti joitain virhekeskeytyksiä (esim. muistiongelmat), joita ei voi peittää.

Keskeytys voi siis merkitä siirtymistä ajamaan jonkin toisen käyttäjän prosessia, mutta ensin tapahtuu aina siirtyminen KJ:n keskeytyksen käsittelijään, jonka osoite katsotaan keskeytysvektorista. Keskeistä turvallisuuden kannalta on, ettei osoitetta pääse kukaan asiaton muuttamaan.

Käynnissä olevien prosessien tiedot ovat käyttöjärjestelmän tauluissa, joista saadaan tietoa sellaisilla varusohjelmilla kuin Unixin ps tai lsof (process status, list open files). Taitava hyökkääjä voi saada oman prosessinsa olemaan näkymättä ps:n tulostuksessa, mutta silloinkaan ei kyse ole siitä, ettei tietoa olisi prosessitaulussa. Hän on vain pystynyt muokkaamaan ps-komentoa tai sitä mekanismia, jolla sen tulos tulee näkyville. Taidon sijasta hyökkääjä voi käyttää taitavasti tehtyä murtopakkia (root kit).

Prosessitaulussa on prosessielementtejä (process control block, PCB) ja niissä seuraavanlaisia tietoja:

  • tunnukset prosessille, sen käyttäjälle ja kulloisellekin efektiiviselle käyttäjälle (vrt. SUID-ohjelmat). Myös suojausmoodi, ellei se määräydy käyttäjästä.
  • tilatietoja, erityisesti onko prosessi ajossa, odottamassa ajoon pääsyä, odottamassa siirrännän päättymistä tai jotain muuta tapahtumaa (ja tieto mitä odotetaan), vai ehkä heittovaihdettuna (swapped)
  • sellaiset tiedot, jotka pitää ladata rekistereihin, kun ajo jälleen jatkuu, erityisesti ohjelmalaskuri ja oman muistialueen rajat
  • prioriteetti, kunkin hetkinen ja perustaso
  • avointen tiedostojen luettelo
  • tarvittavien muiden resurssien tietoja
  • tietoja joilla seurataan suoritinajan ja muiden resurssien käyttöä ja joita voidaan käyttää laskutukseen.
Miten prosessit, säikeet ja keskeytykset lähinnä suhtautuvat toisiinsa?

Muistinhallintaa

Keskusmuisti on sellainen tietovarasto, johon ohjelmat viittaavat välittömästi – ilman että niiden täytyy määritellä jokin tietoväline, jolla tieto on. Tällaistakin muistia voi olla eri lajeja, joista tietoturvan kannalta on tärkeää erottaa sellainen muisti, jota voi vain lukea (ROM) sellaisesta, jota prosessit voivat ainakin periaatteessa päästä kirjoittamaan. Edellisellä olevaan tietoon voidaan luonnollisesti luottaa jälkimmäistä enemmän. Välimuotoja ovat EPROM, EEPROM (vrt. toimikortit) ja WROM (Write once memory).

Osa muistista, vaikka kuuluukin prosessin osoiteavaruuteen ja siis keskusmuistiin, on todellisuudessa tallennettuna oheismuistilaitteelle osana virtuaalimuistin toteutusta — eli se ei kuulu prosessorin muistiavaruuteen. Tällaisen rakenteen yhteydessä on otettava huomioon oheismuistin (levyn) erillisyys, jopa irrotettavuus. Pääseekö sinne joku laitteiston kautta, jääkö sinne jotain selkokielistä, jos kone kaatuu?

Oheismuistien ja varsinaisen keskusmuistin lisäksi KJ hallinnoi vielä cache- eli välimuistia. Se on kaikkein nopein muistityyppi ja sinne pyritään hakemaan keskusmuistista sellaisia tietoja, joita prosessin kohta ennakoidaan käyttävän. Cache ei siis asetu oheis- ja keskusmuistin vaan keskusmuistin ja rekistereiden väliin. (Vrt. www-selauksen cache tai DNS:n cache.)

Keskusmuistin varsinainen käyttö tapahtuu, kun KJ saa käyttäjän prosessilta muistiviittauksen V. Tällöin tapahtuu jotain seuraavista: KJ (tai sen MMU, memory management unit)

  • pilkkoo ja kokoaa V:n uudestaan sen mukaan, mitä se on lukenut erilaisista viittaustauluista. Sellaiset muodostavat puumaisen rakenteen, jolla kunkin viittaustaulun koko saadaan pysymään kohtuullisena;
  • lisää kantaosoitteen V:ssä olevaan suhteelliseen osaan (“offsettiin”);
  • tarkastaa, osuuko V alueelle, joka on sallittu prosessille. Tässä toteutuu viitemonitorin idea. Viittaustaulun alkioissa on osoittimien lisäksi nykyään useita lippuja eli bittejä, joilla pääsyä voidaan rajoittaa, erityisesti sallia vain käyttöjärjestelmälle itselleen.

Nämä toimet riippuvat muistin rakenteesta, jossa on seuraavanlaisia mahdollisuuksia.

  • Jos käyttäjiä on vain yksi (kuten toimikortissa), hänen prosessi(e)nsa erottamiseen käyttöjärjestelmän muistialueesta riittää kiinteä raja (aita, fence), jonka toiselle puolelle ei sallita viittauksia. Luonnollinen toteutus käyttää suhteellisia osoitteita, jotka päivitetään (‘relocation’) todellisiksi vasta ohjelmaa ladattaessa.
  • Useamman käyttäjän tapauksessa tarvitaan sekä ala- että yläraja ja lisäksi, koska prosesseilla on yleensä dynaamista dataa, tarvitaan rajat myös sille.
  • Jos prosessien pitää voida käyttää muistia yhteisesti, tai jos tietty prosessi haluaa suojata osan viittaamastaan muistista esim. kirjoittamiselta, voidaan muistiyksiköt (sanat tai jotkin suuremmat mutta silti pienet kokonaisuudet) varustaa pääsynvalvontabiteillä. Tällainen ns. ‘tagged architecture’ kuluttaa muistia, mutta on joustava. Eduista huolimatta se ei ole yleistynyt, lähinnä yhteensopivuusperinteen takia, ennen kuin 2010-luvun lopulla. (Vrt. kuitenkin Intel- ja ARM-prosessoreiden ajonestobitit, nimenä valmistajasta riippuen NX tai XD: no execute, execute disabled. Edellä mainitut pääsy’liput’ ovat sivutaulussa, eivät muistialkioissa.)
  • Segmentointi: ohjelma (ja data-alueet) jaetaan osiin, jotka ovat loogisia nimettyjä kokonaisuuksia ja jotka sijoitetaan muistiin yhtenäiselle alueelle. Käyttöjärjestelmä pitää yllä osoitetaulua ja kaikki muistiviittaukset tapahtuvat sen kautta, mikä mahdollistaa monenlaiset turvatarkastukset (myös usean viittaavan prosessin osalta). Toisaalta pitää vahtia varsinkin dynaamisia viittauksia segmentin ulkopuolelle. Segmentoinnin toteutuksessa on oma vaivansa ja lisäksi se aiheuttaa muistin pirstoutumista. Unixissa segmentointi on tiukan käytännöllistä: joka prosessilla on vain segmentit “text” ohjelmakoodille, “data” ei-ajettavalle ohjelman osuudelle kuten muuttujille ja vakioille ja “stack” eli vaihtuvankokoinen pino tilapäiselle datalle. Segementointi on jäämässä syrjään sivutuksen tieltä.
  • Sivutus (paging) jakaa ohjelman vakiokokoisiin palasiin, jotka sijoitetaan muistiin ja joihin viitataan segmenttien tavoin. Muisti täyttyy tarkasti ja sivurajan ylitys pysyy kätevästi kurissa osoitteen ylivuotona uudelle sivulle. Käyttöjärjestelmä huolehtii sivujen jakamisesta, mutta ei pysty vastaavalla tavalla eriyttämään niihin pääsyn valvontaa kuin segmentoinnissa. - Sivuttamalla segmenttejä saavutetaan molempien järjestelmien etuja. Hintana on tietenkin lisävaihe viittausten toteutuksessa. - Jos käytössä on virtuaalimuisti, sivutaulussa on myös läsnäolobitti. Jos se on 0 ja operaatio on lukeminen, tapahtuu läsnäolokeskeytys (‘page fault’) ja prosessi jää odottamaan, kunnes sivu on haettu oheismuistista. Vaikka kyseessä olisi kirjoitusoperaatio, sivu pitää jossain vaiheessa hakea kokonaan, jotta se voidaan muutetussa muodossa kirjoittaa takaisin.

Tiedostojärjestelmistä

Tietojen pysyväisluonteinen talletus oheismuistilaitteille muistuttaa keskusmuistin hallintaa. Oheismuistissa tiedon looginen yksikkö on kuitenkin tiedosto (file) ja niitä järjestetään hakemistoiksi (directories). Tietoturvallisuuden kannalta tiedostojen hallinnassa on enemmän haasteita, koska niitä käsitellään useammilla eri tavoilla kuin keskusmuistia, käsittelijöitä on usein enemmän, sijaintipaikat voivat olla vaihtelevan laatuisia ja tallennus kestää pitempään.

Tässä on osittain jäsennettynä luettelona, mitä kaikkea liittyy tiedostoon:

  • nimi — rakenne — tyyppi
  • pääsy — attribuutit
  • operaatiot (luonti, avaus, haku, luku, kirjoitus, sulkeminen, poisto) — systeemikutsut
  • tiedoston kuva muistissa: puskuri eli levyvälimuisti, joka on eri asia kuin keskusmuistin cache.
  • hakemistorakenteet
    • yksitasoinen — hierarkkinen
    • polut
    • hakemisto-operaatiot
  • tiedostojärjestelmän toteuttaminen
    • tiedostojen ja hakemistojen kuvaukset
    • tallennusrakenne
    • tiedoston sijainti, mahd. usealla levyllä; välimuistit
    • jaetut (shared) tiedostot
    • suorituskyky — luotettavuus
    • levytilan hallinta
    • lokirakenteiset tiedostot
    • tulkinnat — koodaukset erityisesti multimedian tapauksessa

Sikäli kuin tiedostojärjestelmää ei tarkastella oheismuistilaitteen tai virtuaalimuistin näkökulmasta, siihen liittyvät turvakysymykset ovat lähempänä käyttäjää kuin laitteistoa. Unixissa muuten oheislaitteetkin ajureineen abstrahoituvat, kun niitä kaikkia käsitellään tiedostojen tapaan.

Unixin tiedostojärjestelmän ohella tunnettuja ovat Windowsin FAT ja NTFS (File access table ja New Technology File System). Tärkeitä ovat myös verkkojärjestelmät NFS (1984 — versio 4.2: RFC 7862, 2016) ja Windowsin SMB (usein “Samba”, nimestä Server Message Block). Jälkimmäiset mahdollistavat toisessa koneessa olevien tiedostojen käsittelyn ikään kuin ne olisivat paikallisia. NFS:n toiminta tapahtuu UDP-protokollan päällä (ja nykyään myös TCP:n) ja jatkuu palvelimen mahdollisen kaatumisen jälkeen “itsestään”. NFS-järjestelmää on muokattu ottamaan Kerberos-autentikointi huomioon käynnistysvaiheessa. SMB-protokollalla voidaan jakaa tiedostojen lisäksi esim. tulostimia. Alustariippumaton jatkokehittely (1996—) siitä on nimeltään CIFS, ‘Common Internet File System’.

Käyttöjärjestelmä laitteiden ohjaajana

Käyttöjärjestelmän ydintoimintoina voidaan pitää suorittimen ja muistin hallintaa ja siihen nähden muut laitteet ovat vain “oheisia” (tai jopa ‘peripherals’). Niitä varten KJ sisältää erillisiä prosesseja, jotka “tuntevat” laitteen rakenteen ja kätkevät sen KJ:n muilta osilta. Näitä prosesseja sanotaan laiteajureiksi (device drivers). Keskeiset KJ:n hallinnoimat välineet siirräntään suorittimen ja oheislaitteiden välillä ovat työjonot ja puskurit. Työjonoihin talletetaan (linkitetään) siirräntää odottavien pyyntöjen parametrit. Puskurit ovat tietorakenteita, joihin siirrettävien bittien virta (stream) menee ja joista sitä puretaan sopivassa tahdissa kohteeseen. Ideana on, että bittien lähettäjä voi kirjoittaa mahdollisimman tehokkaasti, vaikka vastaanottaja ei olisikaan koko ajan lukemassa.

Laitteiden varsinainen käyttö tapahtuu niihin kuuluvien ohjaimien kautta (device controllers). Ne voivat olla melko älykkäitäkin, jolloin ajurien riittää toimia korkeammalla abstraktiotasolla. Esimerkiksi tietoliikenneohjaimet voivat sisältää monimutkaisia protokollia. Lisäksi ajurien ja ohjaimien välissä voi olla erillisiä laitteistosuorittimia, ja toisaalta jotkin ohjaimet voivat siirtää tietoa laitteen ja keskusmuistin välillä suoraan, siis ilman CPU:n apua (DMA, direct memory access). Laitteet ovat kiinni laiteväylässä. Kaksi henkilökohtaisten tietokoneiden kannalta tärkeää standardia niille ovat SCSI ja USB (small computer system interface; universal serial bus).

Vaikka ajureiden sanottiin olevan erillisiä “abstraktion tarjoajia”, ne kuuluvat silti usein KJ:n ytimeen. Nehän ovat tärkeitä suorituskyvyn kannalta ja tarvitsevat joka tapauksessa jossain vaiheessa enemmän oikeuksia kuin käyttäjän prosessit. Seurauksena voi olla turvaongelmia, sillä ajureita tekevät monet valmistajat ja uusia sellaisia voidaan joutua hankkimaan käyttöjärjestelmän sisään. Yksi ratkaisu tähän ja muuhunkin ei-luotetun ohjelman ajamiseen on wrapper-tekniikka: siinä on kyse ohjelmien ja resurssien väliin asennettavasta uudesta ohjelmakerroksesta, joka ei edellytä muutoksia kummallekaan puolelleen. Se siis käärii (‘wraps’) ohjelman ja välittää kaikki tai osan sen systeemikutsuista: Kääre voidaan asettaa odottamaan tietynlaisia kutsuja ja niiden sattuessa se voi estää kutsun, muuntaa sitä tai sen paluuarvoa. Jos käärittävä kohde on ytimessä, itse kääreenkin pitää olla siellä. Käytännössä sovellusohjelmienkin kääreet voivat olla ladattavia ytimen moduuleja.

Tärkeä monitasoinen ajurirakenne liittyy levytiedostoihin. Käyttäjän prosessille nämä on luontevaa näyttää merkeittäin, kun taas levyoperaatiot on tehokkainta suorittaa lohkoittain. Tämän toteuttamiseksi käyttöjärjestelmän kannattaa pitää keskusmuistissa levypuskuria. Siihen liittyy eheysongelma: Jos prosessi tai kone kaatuu, levyllä oleva tiedosto ei välttämättä vastaa sitä näkemystä, joka prosessilla viimeksi oli.

Oletusarvoinen turvallisuus (Secure-by-Default)

Luvun alussa todetaan, että erilaisia käyttöjärjestelmää suorittavia laitteita on monenlaisia. Niin on käyttöjärjestelmien käyttäjiäkin. Toinen on sinut komentorivin tai Windowsin rekisterin editoinnin kanssa, toiselle kansionkin luominen voi olla vierasta. Mitä kuluttajalaitteisiin ja niiden käyttöjärjestelmiin (Windows, Mac, Android, iOS) tulee, ei voida olettaa, että keskivertokäyttäjältä löytyisi riittävät tiedot ja taidot laitteen turvallisuuden konfigurointiin. Niinpä laitteen oletusasetusten tulee olla turvalliset.

Käytännön esimerkki tästä on se, että Windows-koneissa on oletusarvoisesti päällä Microsoftin kyberturvallisuusohjelma sekä valmiiksi konfiguroitu palomuuri. Microsoftin kyberturvallisuusohjelma myös sammuttaa itsensä, jos käyttäjä asentaa kolmannen osapuolen kyberturvallisuusohjelman. Vastaavasti se kytkeytyy automaattisesti takaisin päälle, jos kolmannen osapuolen ohjelmaa ei enää havaita. Motiivina tässä on se, että kaksi samanaikaisesti suoritettavaa kt-ohjelmaa olisivat vain toistensa tiellä heikentäen turvallisuutta. Automaattisella uudelleenkäynnistyksellä taas on haluttu välttää tilanne, jossa käyttäjä jää kokonaan ilman suojausta, jos hän vaikkapa tilapäisen kokeiluversion umpeuduttua poistaakin kolmannen osapuolen kt-ohjelmansa.

Saatoit huomata, että tämän vihreän laatikon ensimmäisen tekstikappaleen käyttöjärjestelmälistauksessa ei mainittu Linux-jakeluita alkuunkaan. Tämä siksi, ettei oletusarvoinen turvallisuus toteudu aivan samanlaisella pieteetillä yleisimmissäkään Linux-jakeluissa, kuten Ubuntussa. Siinä esimerkiksi palomuuri ei ole oletuksena päällä. Aiheeseen liittyvissä keskusteluissa tätä perustellaan usein sillä, että Ubuntu-asennuksessa ei vakiona ole internet-portteihin sidottuja taustaprosesseja* tai palvelimia, jolloin palomuuriakaan ei tarvita**. Toisaalta palomuurin oletustilasta ei tiedoteta missään asennuksen vaiheessa, ja monien ohjelmien käyttö ilman sitä olisi varsin haavoittuvaista. Jää siis täysin käyttäjän vastuulle pystyttää palomuuri, mutta toiseen järjestelmään (esim. Windowsiin) tottunut käyttäjä saattaa helposti erehtyä luottamaan siihen, että vakioasetukset pitävät hänet riittävässä turvassa. Tämä on siis oletusarvoisen turvallisuuden vastaesimerkki. On myös hyvä huomata, että eri järjestelmissä on varsin eri kulttuurit turvallisuuteen liittyen. Mikäli luet tätä tulevana ohjelmistosuunnittelijana/kehittäjänä, kurssihenkilökunta kannustaa kallistumaan odotusarvoisen turvallisuuden suuntaan.

*Sivuhuomio: taustaprosesseista puhutaan Windowsissa palveluina (service), Linuxissa termi on daemon.

**Perustelu on turvallisuuden kannalta melko ontuva. Minimalismiinkaan on hankala vedota ottaen huomioon, että Ubuntu tarjoutuu asennuksen yhteydessä asentamaan kaikenlaista toissijaista, mutta palomuurista ei sanota halaistua sanaa.

Palautusta lähetetään...