Programmøren bag historisk sikkerhedshul taler ud om nytårsbrøler

Illustration: leowolfert/Bigstock
Det kritiske sikkerhedshul i OpenSSL blev introduceret sammen med Heartbeat nytårsaften 2011. Hverken udvikleren eller ham, der så koden igennem, opdagede en banal fejl.

Han er ikke én af de faste udviklere på projektet, men han havde tidligere hjulpet holdet bag OpenSSL med at rette fejl i koden. Derfor er det næsten tragikomisk, at da han selv bidrog med implementeringen af en ny funktion, fik han introduceret en kodefejl, som er en stærk kandidat til at være årtiets mest alvorlige sikkerhedshul.

Den uheldige programmør, Robin Seggelmann, skulle blot have tilføjet en ekstra lille kontrol, som kunne have stået på én linje. Så hvordan kunne det ske?

I et interview med avisen Sydney Morning Herald fortæller den tyske programmør, hvordan fejlen blev introduceret:

»Jeg arbejdede på at forbedre OpenSSL og havde afleveret flere fejlrettelser og tilføjet nye funktioner. I én af de nye funktioner kom jeg uheldigvis til at glemme at kontrollere længden på et array,« siger Robin Seggelmann til Sydney Morning Herald.

Robin Seggelmann var færdig med sin kode og afleverede den til den ansvarlige for den del af OpenSSL, Stephen Hanson, sent om aftenen den 31. december 2011. Han læste koden igennem - uden at finde fejlen - og gav den nye programkode grønt lys til at blive en del af OpenSSL. Det skriver Wired.

Men hvordan kunne to erfarne programmører overse en så alvorlig fejl? Forklaringen skal formentligt findes i to omstændigheder. Først og fremmest er der tale om en banal sikkerhedsfejl, som i bagklogskabens lys burde være undgået.

Ser man på kildekoden til den funktion, Robin Seggelmann afleverede, så ser man, at fejlen opstår, da programmøren beder serveren om at foretage en kopiering af data fra serverens hukommelse:

memcpy(bp, pl, payload);

Det er helt trivielt, bortset fra, at der ikke forud for kopiering er sket en kontrol af, at længden af arrayet payload rent faktisk svarer til den længde, som serveren har fået at vide. Xkcd illustrerer problemet i tegneserieform, men det kan kort forklares således:

*Serveren får en anmodning fra en klient, som vil sikre sig, at forbindelsen stadig er åben. Klienten sender variablen 'payload' til serveren, som skal sende 'payload' retur til klienten. Men sammen med 'payload' får serveren at vide, hvor mange tegn op til 64 kilobytes, 'payload' indeholder. Serveren skal sørge for at sende hele 'payload' tilbage.

Men serveren kontrollerer aldrig, at 'payload' har den længde, klienten har oplyst. Derfor kan klienten narre serveren til at sende 'payload' plus alt der ligger efter 'payload' i hukommelsen op til 64 kilobytes retur til klienten.

Der var ingen andre foranstaltninger i OpenSSL, som forhindrede Robin Seggelmanns kodelinje i at returnere data, som reelt ikke hørte til 'payload'. Så da Heartbeat-funktionen virkede, som den skulle, var der ingen advarselslamper, der blinkede, før mere end to år senere.

OpenSSL er en open source-implementering af sikkerhedsprotokoller som eksempelvis TLS, og softwaren anvendes i alt fra store webservere til IP-telefoner. Blandt de største tjenester, som har bekræftet, at de har været påvirket og har måttet lukke sikkerhedshullet, har været Yahoo og Flickr.

Det er nærliggende at rejse spørgsmålet om, hvorvidt der var tale om en helt banal sjuskefejl i nogle hundrede linjers kode, eller om det var en bevidst handling, der skulle svække sikkerheden for at give efterretningstjenester en bagdør.

»I det her tilfælde var det en banal programmørfejl i en ny funktion, som uheldigvis optræder i et sikkerhedsrelateret område. Det var slet ikke bevidst, specielt ikke fordi jeg tidligere har lukket sikkerhedshuller i OpenSSL,« forklarer Robin Seggelmann til Sydney Morning Herald.

Fejlen blev heller ikke opdaget af nogen andre, som arbejdede med OpenSSL, og det afslører et problem for små open source-projekter med stor betydning. OpenSSL består af fire medlemmer af en styregruppe og i alt er blot 11 udviklere registrerede som en del af projektet.

Der er også hovedsageligt tale om frivillig arbejdskraft med et begrænset budget, selvom softwaren er en del af grundlaget for adskillige store og små internetfirmaer. Selv blandt de virksomheder, som har oplyst, at de ikke er påvirket, er der mange brugere af OpenSSL.

Fejlen blev nemlig først introduceret sammen med Heartbeat-funktionen i OpenSSL 1.0.1, som blev frigivet i marts 2012. Mange webservere benytter sig imidlertid stadig af OpenSSL version 0.9.8 eller 1.0.0, som ikke indeholder Heartbeat-funktionen og dermed sikkerhedshullet.

Til trods for den enorme udbredelse har OpenSSL-projektet aldrig formået at få mere end én million dollars ind i donationer, og udviklerne bag softwaren har aldrig mødt hinanden ansigt til ansigt ifølge Wired.

Til sammenligning købte Facebook, som benytter OpenSSL, firmaet WhatsApp med 55 ansatte for mere end 100 milliarder kroner i februar 2014.

Fejlen blev dog fundet af ét af de store it-firmaer, for det var nemlig Neel Mehta fra Google Security, som rapporterede den til OpenSSL-holdet, der fik lukket sikkerhedshullet.

Der gik dog to år, hvor den nyeste udgave af OpenSSL havde et sikkerhedshul, som var alvorligt nok til, at flere sikkerhedseksperter meget dramatisk gav det '11 på en skala fra 1 til 10'. Sårbarheden kunne nemlig udnyttes til at få fat i krypteringsnøgler, kodeord og personlige oplysninger i klar tekst fra servere, der burde være sikre.

»Det er uheldigt, at det bliver brugt af millioner af mennesker, men der er kun meget få, der bidrager til softwaren. Fordelen ved open source er, at alle og enhver kan se koden igennem. Jo flere, der kigger på koden, jo bedre, især med software som OpenSSL,« siger Robin Seggelmann til Sydney Morning Herald.

Tips og korrekturforslag til denne historie sendes til tip@version2.dk
Kommentarer (24)
sortSortér kommentarer
  • Ældste først
  • Nyeste først
  • Bedste først
Allan S. Hansen

Fejlen blev heller ikke opdaget af nogen andre, som arbejdede med OpenSSL, og det afslører et problem for små open source-projekter med stor betydning

Nu har dette intet med Open Source at gøre - og det siger jeg som en der absolut ikke har noget imod closed source.

Det har noget med review, test m.m. at gøre og kunne ske i alle projekter.

Lars Lundin

Ikke alle projekter har indbygget malware-hjælp til at sikre at operativsystemets anti-malware ikke fungerer.

I tilfældet openSSL er problemet netop deres manglende forståelse af tests.

Hvis de mener at en vis malloc() implementation er for dyr i drift, så skulle de stadig afvikle tests på deres kode med denne dyre malloc(), hvorefter alle mulige advarselslamper ville fortælle dem at de læste uallokeret/uinitialiseret hukommelse.

Finn Aarup Nielsen

Nu når de dansk mainstream-medier endelig har taget sig sammen, behøver de så at forvirrer mere end oplyse. Berit Hartung har skrevet en artikel til et formiddagsblad:

1) "da han lavede en banal tastefejl". Nej, det er det ikke. Patchen ændrer flere linier

2) "OpenSSL skal beskytte de oplysninger, der sendes fra din computer og til din bank" Der er da vist ingen danske banker der benytter OpenSSL. Mashables liste viser heller ingen bank ramt.

3) I SMH står der "he instead requested Fairfax Media not use any photos of him". "Se stort billede" kan man læse i formiddagsbladet.

Lars Skovlund

Eminent dårlige variabelnavne... de to første er pointere, den sidste et længdefelt - men det kan man ikke se/gætte ud fra navnene. Tværtimod kunne man få den ide - som artiklens forfatter - at payload er et array. Det er simpelthen opskriften på at ting går galt.

Der står at koden er blevet reviewet - der burde man have fanget i det mindste dette.

Baldur Norddahl

Hvis de mener at en vis malloc() implementation er for dyr i drift, så skulle de stadig afvikle tests på deres kode med denne dyre malloc(), hvorefter alle mulige advarselslamper ville fortælle dem at de læste uallokeret/uinitialiseret hukommelse.

Det forudsætter en test, som forsøger at trigge buggen. Ved normalt korrekt brug sker det ikke.

Jeg undres over hvorfor man stadig skriver så sikkerhedskritisk kode i et sprog helt uden indbygget sikkerhed. Selv uden deciderede formelle beviser for koden, så er der tonsvis, faktisk de fleste sprog undtagen C, som ville have forhindrer lige denne klasse af fejl.

Milos Game

I artiklen står der "Serveren får en anmodning fra en klient, som vil sikre sig, at forbindelsen stadig er åben". Hvorfor i alverden kan man selv angive nøgleord og længden af nøgleordet der skal sendes retur?

Funktionaliteten skal fungere sådan:
Klient: "Er forbindelsen åben?"
Server: "1"

Det er ikke til at misforstå.. Klienten laver en forespørgsel og serveren svarer "1" (hvilket betyder "true").
Så simpelt kan det gøres.

Lars Skovlund

Der havde Makholm fat i den rigtige forklaring den anden dag. Jeg citerer fra RFC 6520, der omhandler Heartbeat-protokollen:

Furthermore, DTLS needs to perform path MTU (PMTU) discovery but has no specific message type to realize it without affecting the transfer of user messages. [...]
To perform PMTU discovery, HeartbeatRequest messages containing padding can be used as probe packets, as described in [RFC4821].

https://tools.ietf.org/html/rfc6520
Og i den RFC står:

This document describes a robust method for Path MTU Discovery (PMTUD) that relies on TCP or some other Packetization Layer to probe an Internet path with progressively larger packets.

https://tools.ietf.org/html/rfc4821
It's a feature, not a bug! :-)

Mogens Hansen

Hvis de mener at en vis malloc() implementation er for dyr i drift, så skulle de stadig afvikle tests på deres kode med denne dyre malloc(), hvorefter alle mulige advarselslamper ville fortælle dem at de læste uallokeret/uinitialiseret hukommelse.

Jeg kan ikke af det kode, som er checket ind (jvf. linket i artikelen), se at der er tale om uallokeret/uinitialiseret hukommelse.

Hukommelsen, som "memcpy" kopierer fra, kan være en fast allokeret buffer (større end 64k ?) som netværkstelegrammerne modtages i - og så er ikke tale om uallokeret hukommelse. Det forekommer mig også at hukommelsen er initialiseret - desværre f.eks. med en anden clients fortrolige oplysninger.

Hukommelsen, som "memcpy" kopierer til, ser ud til at være fint allokeret med funktionen/makroen "OPENSSL_malloc" - som jeg antager allokerer den mængde hukommelse der angives. Der burde nok være et check på om allokeringen faktisk gik godt - men det er en anden sag (eller er det ? - er kvaliteten i orden ?). Hvis kommentaren over "OPENSSL_malloc" kaldet er rigtig ser det ud som om at der er allokeret tilstrækkelig hukommelse til at "memcpy" ikke laver buffer-overflow.

Problemet er at koden antager at netværkstelegrammet er velformet, således at størrelsen på payload er hvad der er angivet i telegrammet. Det burde koden checke for og håndtere fornuftigt når det ikke er tilfældet.

Hvis modtage bufferen er større end 64k ville ingen dynamisk check i memory-manageren eller lignende finde den fejl. Så er vi ikke ude i udefined behaviour i henhold til sprog specifikationen.

Ingen tvivl om det er alvorlig fejl med store konsekvenser, men vi må prøve at forstå problemet rigtigt, for at kunne se hvordan man kan undgå noget tilsvarende en anden gang.

Brian Vraamark

Jeg undres over hvorfor man stadig skriver så sikkerhedskritisk kode i et sprog helt uden indbygget sikkerhed. Selv uden deciderede formelle beviser for koden, så er der tonsvis, faktisk de fleste sprog undtagen C, som ville have forhindrer lige denne klasse af fejl.

Og hvilket sprog forstiller du dig kan bruges? Det skal være et sprog som kan lave et library som kan bruges fra alle sprog inklusiv C. Det skal også virke på alle platforme, herunder Windows, Linux og Mac.

Baldur Norddahl

Det skal være et sprog som kan lave et library som kan bruges fra alle sprog inklusiv C

Det sprog findes ikke. Herunder er C bestemt ikke et sprog der kan bruges fra alle sprog. Faktisk er det de færreste sprog der direkte kan bruge et bibliotek skrevet i C. Og dem der kan, kan som oftest også eksportere et C API.

Men for at svare på spørgsmålet, så er der mange bud:

1) C++

C++ har et standardbibliotek hvor alt hukommelsestilgang bliver boundary checket og som derfor ville have stoppet heartbleed-buggen.

2) Cyclone http://en.wikipedia.org/wiki/Cyclone_(programming_language)

Cyclone er et C kompatibelt sprog lavet specielt med det formål at lave en sikker variant af C.

3) ADA / SPARK

Kode vi virkelig skal stole på fortjener måske at blive skrevet i det bedste vi kender. Med SPARK kan du skrive matematiske beviser for at koden er korrekt.

4) Go http://golang.org/

Go er et sprog med det formål at lave et bedre sprog end C til system programmering.

Der er mange andre. Mit spørgsmål går ikke så meget på hvorfor OpenSSL er skrevet i C. Det er et gammelt projekt, som er ældre end flere af ovennævnte teknologier. Men hvorfor hænger vi fast i at skrive alt i C når nu der findes bedre alternativer? Hvorfor bliver de vigtiste og mest sikkerhedskritiske dele, som for eksempel OpenSSL, ikke skrevet om i sprog der faktisk understøtter missionen om at lave kode, som vi kan stole på?

Jesper Stein Sandal

Nu har dette intet med Open Source at gøre - og det siger jeg som en der absolut ikke har noget imod closed source.

Det har noget med open source at gøre, men det er kompliceret. Det handler om, at OpenSSL ikke får midlerne til at sikre, at deres kode svarer til, hvor stort et ansvar der ligger på deres skuldre:

http://veridicalsystems.com/blog/of-money-responsibility-and-pride/

Som PHK også har skrevet om: http://www.version2.dk/blog/open-source-og-penge-57360

Mogens Hansen

1) C++

C++ har et standardbibliotek hvor alt hukommelsestilgang bliver boundary checket og som derfor ville have stoppet heartbleed-buggen.

Det er ikke korrekt. Faktisk er det mere undtagelsen end reglen.
C++ har i høj grad samme filosofi som C med "trust the programmer" og fokus på performance.

F.eks. laver "std::vector<T>::operator[]" ingen bounds-check. Hvis man adresserer udenfor det gyldige område har man undefined behaviour.
Undtagelsen er at "std::vector<T>::at" laver bound-check og smider en "std::out_of_range" exception hvis man forsøger at tilgå et element som ikke findes. Funktionen "at" står sektionen "Optional sequence operations" i C++ Standarden.

Det betyder at funktionen
void foo()
{
std::vector<char> vc(7); // Seven chars
char c = vc[10]; // Out of bounds
}

giver "undefined behaviour", hvorimod funktionen
void bar()
{
std::vector<char> vc(7); // Seven chars
vc.at(10);
}
ubetinget smider en "std::out_of_range" exception.

Der er ikke bounds-check på "std::vector<T>::iterator". Hvis man incrementerer forbi "end" og dereferer iteratoren får man undefined behaviour. Nogle implementeringer har i debug-mode runtime-check på iteratorerne - hvilket ligger inden for undefined behaviour.

Selv hvis "std::vector<T>::operator[]" eller "std::vector<T>::iterator" laver bounds-check, er det langt fra klart for mig at det ville have fundet Heart-bleed fejlen.
Hvis modtage-bufferen, som "memcpy" kopierer fra, var en "std::vector<char>" med mere end 64k elementer ville bounds-check ikke have fanget fejlen. Og det vil gælde for et hvilket som helst programmeringssprog/bibliotek/tool.

Uden at det skal misforståes, findes der ingen (praktisk nyttige) programmeringssprog som kan forhindre programmeringsfejl.
I stedet skal man kigge på de processer der har frembragt koden - specifikation, review, test, statisk analyse, dynamisk analyse, økonomi osv.

PS.
Uanset ovenstående, er det min personlige holdning og erfaring at jeg hellere vil skrive C++ end C - for det er simplere og sikrere.

Baldur Norddahl

Uden at det skal misforståes, findes der ingen (praktisk nyttige) programmeringssprog som kan forhindre programmeringsfejl.

Der findes ingen programmeringssprog som kan forhindre ALLE programmeringsfejl. Men sprog ikke er født lige på dette punkt. Der er sprog hvor man kan lave buffer overruns, og så er der flertallet af sprog, hvor man ikke kan. Og tilsvarende kan sprog have andre egenskaber, der gør det mindre sandsynligt med visse klasser af fejl.

Rigtigt, man kan allokere en buffer og på uklog vis genbruge den uden at cleare den. Men det er i praksis bare ikke en klasse fejl du ser i f.eks. java programmer. Og selv hvis du har fejlen, så er det rigtigt usandsynligt at man pludselig finder den private nøgle til serverens certifikat i en buffer. I hvertfald ikke i sprog, hvor sproget garanterer at bufferen er nulstillet når den bliver allokeret (igen eksempelvis Java).

Når jeg nævner C++ så er det for at pointere at man ikke nødvendigvis behøver gå hele vejen til ADA/SPARK for at få en nytteeffekt. Nu er i to der har påpeget at man kun har beskyttelse imod out of bounds checks hvis man bruger den rigtige funktion. Det er den slags der er nemt at skrive ned og checke i code review - både manuelt og automatisk.

Man kan såmænd også komme langt i C med kodestandarter og wrappers, men jo lavere udgangspunktet er, jo sværere bliver det. C er bare ikke et sprog der lægger op til at adgang til hukommelsen skal være sikker, herunder at der altid skal laves boundary checks. Det er et farligt stykke arbejde der lægges over på programmerøren og som computeren gør meget bedre, idet den aldrig glemmer det.

Mogens Hansen

Rigtigt, man kan allokere en buffer og på uklog vis genbruge den uden at cleare den. Men det er i praksis bare ikke en klasse fejl du ser i f.eks. java programmer. Og selv hvis du har fejlen, så er det rigtigt usandsynligt at man pludselig finder den private nøgle til serverens certifikat i en buffer. I hvertfald ikke i sprog, hvor sproget garanterer at bufferen er nulstillet når den bliver allokeret (igen eksempelvis Java).

Jeg skal ikke forsvare fejlen - blot fremføre at det er naivt at hævde at hvis koden i sprog XYZ var det aldrig sket.

Man kan kigge på mange steder fejlen kunne være undgået.
Hvorfor specificerer man et dynamisk størrelse payload i stedet for blot et fast 32 bit heltal ?
Hvorfor har ingen kommenteret det RFC som blev lavet i forbindelse med implementeringen ? Osv. Det er nemt at kloge sig på bagkant.

Præcis samme fejl kunne være lavet i Java, hvis man havde lavet de samme design valg. Og det kan ikke afvises at man ville have gjort det.

En af de væsentlige baggrunde for fejlens store konsekvenser er at OPENSSL_malloc, som default er en del af en custom heap-manager, som genbruger bufferne for at undgå at kalde malloc af performance grunde og den nulstiller ikke hukommelsen efter brug.
I Java er det ikke ualmindeligt at man laver explicit hukommelses genbrug af performance grunde. Søg på "java recycle buffer" eller kig på https://tomcat.apache.org/tomcat-6.0-doc/api/org/apache/catalina/connect... og https://acs.lbl.gov/software/sea/api/src-html/gov/lbl/dsd/sea/nio/util/B...(som dog nuller hukommelsen mellem brug).
Hvis bufferne faktisk var store nok var der ville JVM'en ikke have opdaget fejlen - så var det "kun" et logisk buffer-overflow.
Det kan således ikke afvises at man i en Java implementering var nået til samme problem. Det er langt fra sikkert, ligesom det ikke er indbygget i C at alle SSL implementeringer har det problem.

Et par spørgsmål, som man er nødt til at svare på ved sådan en løsning er: hvor mange sikkerhedhuller er der fundet i JVM'en for nyligt ? Hvor mange gange har man skulle opdatere sin Java for at undgå sikkerhedsproblemer det sidste år ?
Designvalg er altid et kompromis. Det er sjældent en alternativ teknologi kun har fordele.

I forbindelse med Ariane 5's crash i 1996, skrev Betrand Meyer (designer af sproget Eiffel og opfinder af Design-by-Contract) en artikel (http://se.inf.ethz.ch/~meyer/publications/computer/ariane.pdf) hvor han argumenterer for at med anvendelse Design-by-Contract kunne fejlen være undgået.
Altså, hvis softwaren var skrevet i Eiffel var fejlen ikke sket. Softwaren var skrevet i ADA (et af dine bud på et sikkert programmeringssprog).
Det er formodentlig ubetinget et faktum at ingen raket er styrtet ned på grund af Eiffel - ej heller har nogen fløjet.
Det er meget muligt at Betrand Meyer teoretisk set har en pointe, men ADA var ikke noget dårligt valg til Ariane 5 software på det tidspunkt det skulle skrives.
Der var mange måder fejlen kunne være fundet på inden opsendelsen - det var nemt at se på bagkant. Fejlen blev rettet og senere Ariane 5 raketter fløj.
ADA 2012 har fået direkte understøttelse for design-by-contract.

Baldur Norddahl

Jeg synes på den anden side at det er naivt at tro at sproget og øvrige værktøjer ingen betydning har for din evne til at skrive sikkert software. En buffer overflow er stadig ikke en klasse fejl der opstår i java programmer. Og dermed ville Heartbleed være en usandsynlig fejl i et java program, men i C programmer er det helt normalt. Du nævner JVM'en, som jo er skrevet i C og fyldt med den slags. C programmer er hellere ikke immune overfor fejl i andre biblioteker.

Man kan iøvrigt ikke skrive sin egen malloc i Java. Du kan genbruge en buffer, men du kan ikke allokere objekter i din buffer. Den er ikke general purpose, som den malloc wrapper de bruger i OpenSSL. Det vil være højest mærkværdigt hvis en genbrugt buffer pludselig indeholder information fra andre dele af systemet, såsom systemets private certifikat.

Buffer overflows er i vid udstrækning en C specialitet.

Mogens Hansen

Jeg synes på den anden side at det er naivt at tro at sproget og øvrige værktøjer ingen betydning har for din evne til at skrive sikkert software.

Det har jeg bestemt heller ikke hævdet.

Du nævner JVM'en, som jo er skrevet i C og fyldt med den slags.

Det er veldokumenteret at flere sikkerhedsfejl i JVM'en ikke kan henføres til undefined behaviour i C.
Der har givetvis også været som kan henføres til udefined behavior i C.

Man kan iøvrigt ikke skrive sin egen malloc i Java. Du kan genbruge en buffer, men du kan ikke allokere objekter i din buffer.

OpenSSL har ikke lavet sin egen malloc. De har lavet en anden funktion, kaldet OPENSSL_malloc, som angiveligt genbruger nogle buffere uden at nulstille dem.
Noget tilsvarende vil naturligvis kunne laves i Java (uden at det bliver smartere af den grund).

Buffer overflows er i vid udstrækning en C specialitet.

Jeg har ikke set dokumenteret at Heartbleed faktisk skyldes en buffer overflow med deraf følgende undefined behaviour i forhold til C sprogets specifikation.
At der som minimum er tale om et "logisk buffer overflow" er jeg helt med på.

Log ind eller Opret konto for at kommentere