Skip to main content

Veiligere programmeertalen: van de regen in de drup.

·1703 words·8 mins

In 2022 publiceerde het NIST, in opdracht van het Amerikaanse ministerie van handel, en de NSA richtlijnen over de keuze van programmeertalen. Zij ontraden veelgebruikte programmeertalen waarin een groot deel van onze kritieke digitale infrastructuur is geschreven. Grote delen van Windows, Linux, macOS, Firefox, Google Chrome en Office zijn ingeschreven in “onveilige” talen als C en C++. Wat is er mis met deze talen? Hoe pakken alternatieven hun tekortkomingen aan? Zijn de problemen daarmee uit de wereld?

Onveilige talen #

Allereerst eens over de termen “veilig” en “onveilig.” Volgens mijn woordenboek betekent “veilig”: vrij van gevaar, zonder risico. Waarom dan spreekt het NIST dan over “ safer languages”? Zijn die vrijer van gevaar? En over welke gevaren gaat het dan?

Programma’s worden geschreven in programmeertalen, zoals gedichten, essays en krantenartikelen in natuurlijke talen. Programma’s kunnen dezelfde fouten bevatten als hun natuurlijke tegenhangers: spelfouten, grammaticale fouten of onzinnige constructies. En de vergelijkingen houden daar niet op. Recent neurowetenschappelijk onderzoek toont aan dat programmeurs dezelfde hersendelen gebruiken bij het verwerken van programmeertaal als bij hun moedertaal1.

Er zijn ook verschillen. Programmeertalen zijn formeel; programma’s volgen rigide regels die elke vorm van onduidelijkheid uitbannen. Soms trekt men hieruit onterecht de conclusie dat die wiskundige structuur foutloze programma’s garandeert. Het idee van mechanische computers die geen rekenfouten maken, was een belangrijke drijfveer voor de informaticapioniers. Italiaans generaal en wiskundige Menabrea omschreef die belofte in 1842 als volgt:

[…] correctness in the results, united with economy of time. — (Menabrea & Lovelace, 1842)

Computersystemen tonen ons dagelijks dat het ideaal van correctheid verre van behaald is. Het probleem is slechts verplaatst van de menselijke rekenaar naar de menselijke programmeur: van fouten in de berekening naar fouten in het stappenplan.

Gelukkig helpt de formele aard van programmeertalen programmeurs bij het detecteren van grammaticale fouten. Onderstaand programma, geschreven in de programmeertaal Python, instrueert de computer om de tekst “Hallo lezer” in een tekstvenster te tonen:

print("Hallo lezer")

Zou ik echter het haakje sluiten vergeten dan zal de computer klagen. In dit geval klaagt de computer over een syntax error: jargon voor een grammaticale fout.

print("Hallo lezer"

Sommige typefouten worden niet opgemerkt. Stel ik heb een programma dat mensen kan verrassen met een vrolijke boodschap. In dit programma bestaan twee personen aangeduid door de woorden ik, een troste vader, en uk, zijn dochter. Ik wil de laatste verrassen, maar schrijf:

verras(ik)

Dit is een geldig programma: het instrueert de computer om de persoon aangeduid met “ik” te verrassen. Maar het programma is desalniettemin onjuist: het verrast de verkeerde persoon. Dit noem ik een intentiefout: de intentie van de programmeur strookt niet met de code. Nog erger wordt het, als mijn programma ook een instructie kent om vuilnis te verassen en ik een typefout zou maken bij het aanduiden van de procedure:

veras(uk)

We mogen hopen dat een computer deze fout tijdig detecteert en weigert uit te voeren. Helaas. Wanneer een fout wordt gedetecteerd hangt niet alleen af van de programmeertaal, maar ook van het programma dat verantwoordelijk is voor het interpreteren of vertalen (compileren) van programma’s. In het geval van Python wordt de fout pas opgemerkt tijdens de uitvoer van de procedure veras.

Een voorbeeld zal dit duidelijk maken. Een programma dat het gemiddelde van twee getallen berekent, komt tot zijn uitvoer door de twee getallen bij elkaar op te tellen en te delen door twee. Zou ik dat programma in Python schrijven, maar het vervolgens tekst voeden in plaats van nummers, zal Python eerst de twee tekstfragmenten aaneenschakelen (zo interpreteert Python de instructie om “teksten op te tellen”). Wanneer Python dit resultaat vervolgens probeert te delen, blijkt dat links van het deelteken geen getal maar tekst staat! Het programma wordt dan bruusk afgebroken: een crash. Dit is een typefout: ik probeer andersoortige data te verwerken dan een operatie verwacht.

Andere programmeertalen controleren eerst het volledige programma op typefouten alvorens deze verder te verwerken. In die talen is het onmogelijk mensen als vuil te behandelen.

Intentiefouten #

We hebben nu drie verschillende programmeerfouten gezien. Tabel 1 zet deze nog kort uiteen. Alle moderne talen controleren programmacode op grammaticale fouten, al verschillen ze in het moment waarop ze dat doen: voor aanvang of tijdens het uitvoeren van het programma. Veel programmeertalen vereisen daarnaast ook dat programma’s juist getypeerd zijn. Ook hier verschillen zij in het moment waarop ze op typefouten controleren en hoe ze daarmee omgaan2.

Tabel 1: Drie soorten fouten in computerprogramma's
Soort fout Voorbeeld Reden
grammaticale fout verras(uk Haakje sluiten ontbreekt.
typefout veras(uk) Persoon wordt als vuilnis behandeld
intentiefout verras(ik) Verkeerde persoon wordt verrast

Intentiefouten vloeien voort uit de betekenis, semantiek, van een programma en volgen niet uit de syntaxis, de formele structuur. Dit soort fouten is dan ook ongrijpbaar voor compilers en interpreters, die opereren op formele constructies. En laat het nu deze soort zijn die aan de de grondslag ligt van kwetsbaarheden zoals die in Citrixsoftware in 2020.

Intentiefouten glippen ongemerkt door de mazen in de formele taalregels. Derhalve dienen programmeurs hun werk te testen nadat dit vrij van syntactische fouten is bevonden. Dat kan ofwel formeel, door eigenschappen wiskundig te bewijzen, ofwel informeel door het gedrag van een programma te observeren op allerhande invoer. Formele verificatie is kostbaar en testen ontoereikend:

The first moral of the story is that program testing can be used very effectively to show the presence of bugs but never to show their absence. — Dijkstra (1970/2022)

Het gevolg is dat onze maatschappij wordt overspoeld met allerhande kwetsbaarheden die het resultaat zijn van dergelijke intentiefouten. Dijkstra waarschuwde hiervoor al in 1970: software groeit in grootte en complexiteit. Een complexe webbrowser zal uit tien- of honderdduizenden kleine programma’s bestaan. Zelfs al zouden we 99.999% zeker zijn van de correctheid van elk programma dan nog zou ons vertrouwen in het geheel dramatisch laag moeten zijn:

\[99,999\%^{100.000} \approx 0,005\%\]

De ISO-werkgroep die op initiatief van het NIST nadenkt over veiligere programmeertalen is dan ook wel erg naïef wanneer zij schrijft dat:

As a result, software programs sometimes execute differently than was intended by their developers. (ANSI, 2005)

Het gevaar in traditionele talen #

In een tijd dat processoren ordes van grootte trager waren dan die wij tegenwoordig in een budgettelefoon telefoon vinden, was elke instructie kostbaar. Dat leidde tot concessies in het ontwerp van programmeertalen, zeker wanneer het aankwam op geheugenbeheer3.

We kunnen ons computergeheugen voorstellen als een lange reeks van genummerde vakjes. In elk vakje past een getal. Het eerste vakje heeft nummer 0, het laatste vakje in mijn tablet met 8 gigabyte geheugen het nummer 8,589,934,591. Er zijn dus meer dan achteneenhalf miljard vakjes! Die hoeveelheid is lastig te overzien. Het geheugen wordt opgeknipt voor verschillende doeleinden. Zo kan het zijn dat een reeks vakjes, zeg van 512 tot en met 2.097.663, gereserveerd is om de webpagina die ik bezoek op te slaan. De computer is vrij om de vakjes vanaf 2.097.664 voor andere doeleinden te gebruiken. Wat gebeurt er nu als een programma die vakjes toch wil benaderen alsof deze nog bij de website hoorden? Mogelijkheden zijn:

  1. het programma mag die vakjes gewoon benaderen;
  2. de computer is vrij om zelf te bepalen wat er gebeurt;
  3. of het programma crasht.

Optie 1 is onwenselijk: stel dat die latere vakjes wachtwoorden, je dagboek, of de inhoud van een andere website, zoals die van je bankrekening, bevatten. In dat geval zou het programma toegang krijgen tot vertrouwelijke informatie. Optie 2 geeft helemaal geen garanties; toch is het een veel gekozen optie. De ISO-normen voor C en C++ zijn zo geformuleerd dat het gedrag van programma’s met intentiefouten ongedefinieerd is. De gevolgen zijn groot. Niet alleen vervallen de garanties op de benadering van specifieke vakjes. Zodra een programma een dergelijke fout bevat, worden er geen garanties meer gegeven over de werking van het gehele programma! Waarom men koos voor opties 1 en 2? Ze vereisen minder instructies. Noch optie 1 noch optie 2 vereist controles van vakjesnummers.

Deze fouten worden door kwaadwillende gebruikt om toegang te krijgen tot vertrouwelijke gegevens, computers te infecteren met ransomware of aanvallen uit te voeren op kritieke infrastructuur.

Optie 3 verijdelt deze gevolgen maar vereist wel controles. Wanneer een programma een verkeerd vakje benaderd, wordt dit herkend en het programma afgebroken. Ongedefinieerd gedrag wordt voorspelbaar met onstabiele software tot gevolg. Gaan we dat merken? Wellicht. Ik bezit enkel anecdotisch bewijs 4. Het is het onderzoeken waard, al zullen we we dit snel genoeg proefondervindelijk te weten komen.

Conclusie #

Het NIST adviseert talen die opties 1 en 2 vermijden. Die programma’s zijn ofwel onvoorspelbaar of onveilig. Alternatieve, courante, programmeertalen kiezen voor optie 3. Maar daarmee pakt zij de werkelijke oorzaak niet aan. Deze talen verhinderen immers niet dat een programma intentiefouten bevat. Zij veranderen slechts de gevolgen van die fouten. Die fouten worden daarnaast vaak gemaakt en niet soms zoals NIST beweert. Inzetten op optie 3 betekent dat er minder vertrouwelijke informatie gelekt kan worden en minder computers besmet met malware, maar in plaats daarvoor krijgen we software die zich makkelijker laat crashen door kwaadwillenden.

En zo belanden we van de regen in de drup.

Vooruitblik #

Is optie 3 het enige alternatief? Nee. Recente innovaties in typesystemen maken het mogelijk om meer van de intentie in types vast te leggen. Dat betekent dat wat voorheen intentiefouten waren nu typefouten zijn; en die kunnen wel voor aanvang van een programma worden herkend. Een deel van mijn onderzoek verkent deze mogelijkheden. Daarover later meer.

Bronverwijzingen #

ANSI. (2005). Proposal for a New Work Item. Retrieved from https://www.open-std.org/JTC1/SC22/WG23/docs/ISO-IECJTC1-SC22-WG23_N0001-sc22n3913-nwip-for-tr24772.htm
Dijkstra, E. W. (2022). On the reliability of programs. In K. R. Apt, T. Hoare, & T. Hoare (eds.), Edsger Wybe Dijkstra: His life,work, and legacy (pp. 359–370). New York, NY, USA: Association for Computing Machinery. doi: 10.1145/3544585.3544608 (Original work published 1970)
Menabrea, L. F., & Lovelace, A. (1842). Sketch of the analytical engine invented by Charles Babbage.

  1. Hier schreef ik eerder over. ↩︎

  2. De taal waarin moderne webapplicaties worden geschreven, JavaScript, is een voorbeeld van een taal die zeer los omgaat met types en typefouten pas laat of niet detecteert. ↩︎

  3. In deze presentatie vertelt gerenomeerd informaticus Tony Hoare over de mentaliteit van die tijd. ↩︎

  4. Sinds ik mijn computer heb bijgewerkt naar de laatste versie van macOS (13.0) zie ik vaker software crashen die geheugen verkeerd benadert. ↩︎