Moderne programmeringssprog bruger i høj grad teknikker, som forhindrer programmøren i at introducere nogle af de sikkerhedshuller, som har givet problemer i årtier. Men selvom sprogene hjælper programmørerne, så er det ikke alle sikkerhedshuller, der forsvinder ved at programmere mindre i C++ og mere i Rust.
En af de type fejl, som mange sprog efterhånden sørger for at minimere eller helt eliminere, er typefejl. Det vil sige, at et program eksempelvis forventer at skulle behandle et heltal, men får leveret en tekststreng.
Problemet med typefejl er, at de kræver analyse af, hvad programmet gør og dermed vil forvente af input. Syntaksen i programmet kan være korrekt, men ved kørsel vil det give en fejl. Derfor benytter man i dag sprog med statiske typer. Det vil sige, at når noget skal være et heltal, så vil det kun kunne være et heltal.
»Med statiske typer får man ingen typefejl ved kørsel. Det ligger alt sammen i semantikken,« siger professor Olivier Danvy fra Institut for Datalogi ved Aarhus Universitet til Version2.
»De fleste moderne sprog tillader ikke automatisk konvertering mellem typer,« påpeger lektor Torben Mogensen fra Datalogisk Institut ved Københavns Universitet til Version2.
Tanken bag er, at man skal komme så tæt på at kunne forudsige programmets opførsel, når det afvikles.
Visse simple programmer kan man bevise, hvordan de vil opføre sig, men for den software, man i praksis udvikler, vil det være en uoverkommelig opgave. Derfor har man siden datalogiens barndom arbejdet med forskellige tiltag for at forhindre kritiske fejl.
Eksempelvis arbejdede de første udgaver af Lisp med en anden tilgang til problemet med typer, nemlig dynamiske typecheck, som først sker under kørslen af programmet, men hvor programmøren bør tage højde for, hvad der sker, hvis der opstår en typefejl. Fortran, som blev skabt i samme periode i slutningen af 1950'erne og begyndelsen af 1960'erne var derimod med statiske typer.
Garbage collection rydder op i hukommelsen
Senere kom et sprog som ML til og introducerede en funktion, som i dag er udbredt i nyere sprog, nemlig garbage collection. Det er en funktion, der hjælper med at rydde op i hukommelsen for henvisninger og data, der ikke længere bruges af applikationen.
»ML-skaberen Robin Milners store bedrift var at skabe et sprog med garbage collection, som aldrig kunne lave en typefejl,« siger Olivier Danvy.
Typesikkerheden i ML kom fra en særlig algoritme, der kunne udlede den korrekte type for en variabel. Dermed endte ML med at blive et sikkert sprog, der også er blevet anvendt i datalogiundervisningen i mange år.
Garbage collection ramte for alvor mainstream-programmering med Java, hvor det var én af de ting, som i begyndelsen fik især udviklere fra C og C++ til at kritisere Java-programmer for at køre for langsomt.
Erfaringen med garbage collection har imidlertid været, at den eliminerer et problem, man ellers kunne løbe ind i med netop de på daværende tidspunkt populære sprog.
»Det kan være et problem i C, når man frigiver noget lager. Hvis man ikke bruger garbage collection, skal man sikre, at man ikke peger på noget lager, der er frigivet,« siger Torben Mogensen.
Hvis man har fortalt systemet, at et område i hukommelsen ikke skal bruges længere, men stadig har noget, der peger på området, og senere prøver at læse fra det, så kan man ende data, der ikke hører til i applikationen. Omvendt kan man fjerne en henvisning til et område i hukommelsen, men ikke slette den. I så fald kan man ende med, at de ikke-slettede data ender et forkert sted.
Sproget Rust holder styr på henvisninger
Nyere sprog som Rust forsøger at løse problemet uden garbage collection ved i stedet at holde nøje styr på alle henvisninger til hukommelsen.
De fleste af de sikkerhedsproblemer, der kan opstå under kørslen af et program hænger sammen med, at programmet får nogle input, det ikke er beregnet til at håndtere. Det gælder eksempelvis for webapplikationer, hvor SQL-injektion længe har været et problem.
SQL-injektion opstår, når applikationen kan modtage en tekststreng, hvor det er muligt at inkludere SQL-kommandoer, som bliver fortolket og udført af applikationen. Det løser udviklerne typisk ved hjælp af forskellige former for inputvalidering, men der findes også tiltag i sprogene til at mindske risikoen.
»I Java bliver det kodet som en streng, der indeholder både data og nøgleord. Men i for eksempel C# har man Linq, der bruger en særlig syntaks til at sørge for at adskille data fra nøgleord,« forklarer Torben Mogensen.
Flere sprog kan også i et vist omfang begrænse risikoen for forskellige typer bufferoverløb ved eksempelvis automatisk at udvide en buffer. Et bufferoverløb opstår typisk, når man forsøger at kopiere en stump data, der er større end det område, der er afsat til at kopiere dataene til.
Derfor er de særligt problematiske i sikkerhedssammenhænge, fordi de ofte kan fremprovokeres ved at få en funktion til at kopiere en inputdatamængde, der er for stor til bufferen.
Nyere compilere som eksempelvis dem baseret på LLVM benytter forskellige metoder til at undgå, at bufferoverløb kan udnyttes til at kompromittere et system.